# React
**Repository Path**: showlotus/react
## Basic Information
- **Project Name**: React
- **Description**: No description available
- **Primary Language**: JavaScript
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-05-08
- **Last Updated**: 2021-05-29
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 我的React学习之路
## 在HTML中运用
- 引入JS文件
```html
```
- JSX语法规则
1. 定义虚拟DOM不需要引号
2. 用 `{}` 包裹 `JS表达式` `(!不是语句!)`
- 表达式
```js
let a
let a = b + c
console.log(1)
arr.map()
function fn(){}
...
```
- 语句
```js
if(){}
for(){}
switch(){}
...
```
3. 用 `className` 替换 `class`
4. 内联样式用 `style={{key: value}}`
5. 必须只有一个根标签
6. 标签必须闭合
7. 标签首字母
(1). 若小写字母开头,则识别为html标签,html没有该标签则报错
(2). 若大写字母开头,则识别为组件,没有该组件则报错
- 事件绑定
- 方法1
```js
document.getElementById('btn').onclick = function(){}
```
- 方法2
```js
document.getElementById('btn').addEventListener('click', function(){})
```
- 方法3(推荐使用)
```html
```
```js
// js
function pop(){}
```
- 组件
- 函数式组件(只能使用类式组件的props)
```jsx
function Person(props) {
const { name, age, gender } = props
return (
- 姓名:{name}
- 年龄:{age}
- 性别:{gender}
)
}
const p = { name: "Tom", age: 30, gender: 'female', speak: speak }
ReactDOM.render(, document.getElementById('main'))
const speak = () => { }
```
- 类式组件
```jsx
class Person extends React.Component {
// 对标签属性值进行约束
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string,
speak: PropTypes.func
}
// 对标签属性值设置默认值
static defaultProps = {
gender: '不告诉你',
age: 18
}
render() {
const { name, age, gender } = this.props
return (
- 姓名:{name}
- 年龄:{age}
- 性别:{gender}
)
}
}
ReactDOM.render(, document.getElementById('main'))
```
三大核心属性
- state
```jsx
class Weather extends React.Component {
state = { isHot: false, temperature: 20 }
render() {
const { isHot } = this.state
return
}
changeWeather = () => {
const { isHot } = this.state
this.setState({
isHot: !isHot
})
console.log(this.state)
}
}
```
- props
```jsx
class Person extends React.Component {
// 对标签属性值进行约束
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string,
speak: PropTypes.func
}
// 对标签属性值设置默认值
static defaultProps = {
gender: '不告诉你',
age: 18
}
render() {
const { name, age, gender } = this.props
return (
- 姓名:{name}
- 年龄:{age}
- 性别:{gender}
)
}
}
const p = { name: "Tom", age: 30, gender: 'female', speak: speak }
ReactDOM.render(, document.getElementById('main'))
// => ReactDOM.render(, document.getElementById('main'))
```
- ref
```jsx
class Com extends React.Component {
/*
React.createRef 调用后返回一个容器,该容器返回可以存储ref所标识的节点
该容器只能唯一使用,专人专用
*/
myRef = React.createRef()
showLeftInputData = () => {
const input1 = document.getElementById('input1')
console.log(this.refs.btn)
}
showRightInputData = () => {
console.log(this.refs.ipt2.value)
}
showData = () => {
console.log(this.fn_input1.value)
}
showData2 = () => {
console.log(this.fn_input2.value)
}
showData3 = () => {
console.log(this.myRef.current.value)
}
setRef = (c) => {
this.fn_input2 = c
}
render() {
return (
字符串形式ref
回调函数形式ref(直接绑定在组件的this上)
若以内联函数形式,则在更新状态时会触发两次(无关紧要)
this.fn_input1 = c} onBlur={this.showData} type="text" placeholder="回调函数形式ref" />
内绑定回调函数
ref
)
}
}
```
- 受控/非受控组件
- 非受控组件(使用到了ref,不建议使用)
```jsx
class Box extends React.Component {
render() {
return (
)
}
}
```
- 受控组件
```jsx
class Box extends React.Component {
render() {
return (
)
}
}
```
- 生命周期(旧)
1. 初始化阶段:由 `ReactDOM.render()` 触发 --- 初次渲染
1. `constructor()`
2. `componentWillMount()`
3. `render()`
4. **`componentDidMount()`**:开启定时器、发送网络请求、订阅消息
2. 更新阶段:由组件内部 `this.setState()` 或父组件 `render` 触发
1. `shouldComponentUpdate()`
2. `componentWillUpdate()`
3. **`render`**:必备
4. `componentDidUpdate`
3. 卸载组件:由 `ReactDOM.unmountComponentAtNode()` 触发
- **`componentWillUnmount()`**:关闭定时器、取消订阅消息
- 生命周期(新)
- 三个钩子函数需要添加 `UNSAFE_` 前缀,可能以后要废弃
1. `componentWillMount`
2. `componentWillReceiveProps`
3. `componentWillUpdate`
- 执行顺序
1. `constructor()`
2. `componentDidMount()`
3. **`getDerivedStateFromProps()`**
```jsx
/*
得到一个派生的状态
适用于state的值在任何时候都取决于props
将从props传来的属性值,派生给state中的属性
派生状态会导致代码冗余,组件难以维护
*/
static getDerivedStateFromProps(props, state) {
/*
props 组件传递过来的属性
state 原件原生状态中的属性
可以对两者进行校验
*/
console.log('derived state', props, state)
return null
// return { count: -1 }
/*
返回一个对象,用该对象对state中相同的属性进行覆盖
覆盖掉的属性无法修改和触发更新
*/
}
```
4. `shouldComponentUpdate()`
5. `render()`
6. **`getSnapshotBeforeUpdate()`**
```jsx
// 更新前的回调函数,并将返回值作为 DidUpdate() 的参数传递
getSnapshotBeforeUpdate() {
return 1
}
componentDidUpdate(preProps, preState, val) {
console.log(val) // 1
}
```
7. `componentDidUpdate()`
8. `componentWillUnmount()`
- Diff算法
- 脚手架
- 项目创建
1. 先全局安装脚手架
```shell
npm i -g create-react-app
```
2. 切换到项目目录
```shell
create-react-app XXX
```
3. 创建后的项目文件目录
```
├── public 静态资源文件
│ ├── index.html 主页面
│ ├── manifest.json 应用加壳配置
│ └── robots.txt 爬虫配置
└── src
├── App.css
├── App.js
├── App.test.js 测试
├── index.css 通用样式
├── index.js 入口文件
├── reportWebVitals.js 记录页面性能
└── setupTests.js 组件测试
```
- 代理转发
1. 配置 `package.json`:只适用于单一服务器代理
```json
{
{
},
"proxy": "http://127.0.0.1:9000" // 目标服务器地址
}
```
请求样例
```js
const url = `http://127.0.0.1:3000/test`
// 要请求的地址改为当前开发环境的模拟服务器地址
// 当请求的路径文件在开发环境中存在时,则不会像服务器发起请求
// 而是请求开发环境的本地资源文件
axios.get(url).then(
res => {
console.log('success', res.data)
},
err => {
console.log(err)
})
```
2. 配置 `setupProxy.js`:适用于多个服务器代理
```js
// commonJS 语法
const proxy = require("http-proxy-middleware")
module.exports = function (app) {
app.use(
// 将请求API中包含 /api1 字段的请求代理转发到 target 的地址
proxy('/api1', {
target: 'http://localhost:9000', // 请求转发给谁
changeOrigin: true, // 控制服务器收到的请求头中Host字段的值
pathRewrite: { // 重写请求路径
'^/api1': ''
}
}),
proxy('/api2', {
target: 'http://localhost:9001',
changeOrigin: true,
pathRewrite: {
'^/api2': ''
}
}),
)
}
```
- 消息订阅-发布机制 `pubsub-js`
```js
// 引入依赖
import PubSub from 'pubsub-js'
// 发布
PubSub.publish('search', {
isFirst: false,
isLoading: true
})
// 订阅
// 订阅的组件在挂载时进行订阅
componentDidMount() {
this.searchToken = PubSub.subscribe('search', (_, data) => {
this.setState(data)
})
}
// 组件卸载时取消订阅
componentWillUnmount() {
PubSub.unsubscribe(this.searchToken)
}
```
- `Fetch`:原生函数,不再使用XmlHttpRequest对象提交ajax请求;老版本浏览器兼容性不好
```js
fetch(this.state.url).then(
res => {
console.log('连接服务器成功')
return res.json() // 返回一个promise实例
}
).then(
res => {
console.log('获取数据成功', res.items.length)
}
).catch(
err => {
console.log(err)
}
)
// 优化
getDataByFetch = async () => {
try {
const response = await fetch(this.state.url)
const result = await response.json()
console.log(result.items.length)
} catch (err) {
console.log(err)
}
}
```
- 路由 `router`:底层基于浏览器的历史记录的原理实现:1. `History.createBrowserHistory()`;2. `History.createHasHistory()`,浏览器的 `history` 是个 `栈` 结构,通过 `push()`、`replace()`、`goForward()`(前进)和 `goBack()`(回退)对 `history` 进行操作。
- `react-router-dom`:
- Router 直接包裹在 App 组件的外层
```js
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
,
document.getElementById('main')
)
```
- 路由组件放在 pages 目录下
```js
import { Link, Route } from 'react-router-dom'
```
- NavLink:为选中的路由添加 `active` 类名,也可以自定义
```html
Home
```
- 以标签体传递的属性,可以在组件的 `this.props.children` 得到,NavLink 标签会将 children 属性作为标签体中的内容
```html
{this.props.children}
```
- Switch 包裹注册的路由,在进行路由匹配的时候就只展示匹配到的第一个路由组件
```html
```
- 外部引入的CSS文件在路由更改后,样式丢失的问题
1. 将引用路径改为绝对路径
```html
```
2. 直接写(默认是在 `public` 目录下)
```html
```
3. *`HashRouter`* -- 不常用
```js
```
- 路由默认是模糊匹配
即:路由链接中的路径只要包含路由绑定的路径,且必须是第一个就包含
```html
home
```
开启精准(严格)匹配,不能随意开启,可能会导致二级路由失效
```html
home
```
- `Redirect` 路由重定向,通常写在注册路由的下方,当所有路由都无法匹配时,执行Redirect
```html
```
- 路由组件传参
1. 向路由组件传递params参数 -- *在地址栏暴露传递的信息*
```html
{v.title}
```
```js
// 组件接收参数
const { id, title } = this.props.match.params
return (
)
```
2. 向路由组件传递search参数 -- *在地址栏暴露传递的信息*
```html
{v.title}
```
```js
// 接收search参数
const search = this.props.location.search.slice(1)
const { id, title } = qs.parse(search)
```
3. 向路由组件传递state参数 -- *在地址栏不暴露信息*
```html
{v.title}
```
```js
// 接收state参数
// 防止浏览器清理缓存,需要给数据添加备用数据
const { id, title } = this.props.location.state || {}
```
- 路由跳转默认是进行 `push` 操作,若想执行 `replace` 操作,则可以直接在 `Link` 标签里添加 `replace` 属性
```html
{v.title}
```
- 编程式路由导航
```js
// push跳转
pushShow = (id, title) => {
// params
this.props.history.push(`/home/message/detail/${id}/${title}`)
// search
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
// state
this.props.history.push('/home/message/detail', { id, title })
}
// replace跳转 与push同理
replaceShow = (id, title) => {
this.props.history.replace(`/home/message/detail/${id}/${title}`)
}
this.props.history.go()
this.props.history.goForward()
this.props.history.goBack()
this.props.history.push()
this.props.history.replace()
```
- `withRouter`:将一般组件加工为路由组件
```js
import { withRouter } from 'react-router-dom'
class Header extends Component {
render() {
return (
)
}
}
export default withRouter(Header)
```
- `redux`:
- 配置 `index.js`
```diff
import React from 'react'
import ReactDOM from 'react-dom'
import {
BrowserRouter
} from 'react-router-dom'
import App from './App'
+ import { Provider } from 'react-redux'
+ import store from './redux/store'
ReactDOM.render(
+
+
+
,
document.getElementById('main')
)
```
- 在 `src` 目录下新建一个文件夹 `redux`
- `store.js`:基本结构
```js
import {
createStore,
applyMiddleware,
combineReducers
} from 'redux'
// 引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
// 引入redux-devtools-extension,在浏览器端可以使用redux开发者工具
import { composeWithDevTools } from 'redux-devtools-extension'
// 引入各个组件的reducers
import count from './count/reducers'
import person from './person/reducers'
// 汇总所有reducers
// combineReducers里的内容就是最后reducers的格式
// 各个组件若想取用redux中的状态就必须按照汇总的属性名,进行取用
const allReducers = combineReducers({ count, person })
export default createStore(allReducers, composeWithDevTools(applyMiddleware(thunk)))
```
- `constant.js`:用以暴露组件进行type类型检验时的type常量,方便后期维护
- `组件名`的目录:用于存放该组件的 `reducers.js` 和 `action.js`。
- `reducers.js`:暴露一个方法,根据当前的状态类型对状态进行操作
```js
const init = [{
name: 'INIT',
age: 18
}]
export default function personReducer(prevState = init, action) {
const {
type,
data
} = action
switch (type) {
case ADD_PERSON:
return [...prevState, data]
default:
return prevState
}
}
```
- `action.js`:命名各种状态操作的方法名
```js
// 创建增加人动作对象
export const addPerson = (person) => ({
type: ADD_PERSON,
data: person
})
```
- 使用 `redux` 的组件整体结构
```js
// 必备模块
import React, { Component } from 'react'
import { connect } from 'react-redux'
// 引入该组件对应的action.js文件中的状态操作方法
import { addPerson } from '../../redux/person/action'
// 以类形式定义的组件
class Person extends Component {
// 各种属性和方法都挂载在this.props上
addPerson = () => {
const name = this.nameNode.value
const age = +this.ageNode.value
this.props.addPerson({ name, age })
}
render() {
return (
)
}
}
// 从redux中获取状态
const mapStateToProps = state => ({
personList: state.person,
sum: state.count
})
// 按redux的形式暴露该组件
export default connect(
mapStateToProps, // state
{ addPerson }, // 从action文件中引入的方法名
)(Person)
```
- 扩展内容
- `setState` 两种写法
```js
// 对象式setState
// 适用于:新状态不依赖原状态
const { m } = this.state
this.setState(
{ m: m + 1 },
() => {
console.log('state改变后的回调函数')
}
)
// 函数式setState
// 适用于:新状态依赖原状态
this.setState((state, props) => ({ n: state.m + 1 }))
// =>
// this.setState( state => ({ n: state.m + 1 }))
```
- `lazyLoad` 懒加载
```jsx
// 引入lazy和Suspense
import React, { Component, lazy, Suspense } from 'react'
// 引入需要懒加载的组件
const Home = lazy(() => import('./pages/Home/Home'))
const Shop = lazy(() => import('./pages/Shop/Shop'))
const About = lazy(() => import('./pages/About/About'))
// 懒加载包裹
loading...}>
// 注册路由
// 路由重定向
```
- `Hooks`:在函数组件中使用 `state` 以及其它的 `React` 特性
- `State Hook`:React.useState()
```js
const [data, setData] = React.useState(0)
// data: 将0作为初始值赋给data
// setData: 为更改状态值的函数
import React from 'react'
export default function Header() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Tom')
function add() {
// setCount(count + 1)
setCount(c => c + 1)
setName(c => c + count)
}
return (
Count: {count}
Name: {name}
)
}
```
- `Effect Hook`:React.useEffect(),在函数组件中使用模拟类组件的生命周期钩子
```js
React.useEffect(() => {
console.log('相当于componentDidMount()')
return () => {
console.log('相当于componentWillUnmount()')
// ReactDOM.unmountComponentAtNode()
}
}, [])
React.useEffect(() => {
console.log('相当于render()')
})
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log('相当于componentDidUpdate()监听count值的更新')
}, [count])
```
- `Ref Hook`:在函数组件中使用Ref
```js
const input = React.useRef()
```
- `Fragment`:去除组件外层包裹的 `div`
```jsx
import { Fragment } from 'react'
return (
) // Fragment可以在标签里定义key值,用于遍历
// =>
return (
<>
>
) // 空标签里不能定义任何属性,否则会警告丢失
```
- `Context`:一种组件间通信方式,常用于【祖先组件】和【后代组件】间通信
1. 只适用于类式组件使用
```js
// 在祖先组件中定义一个可供后代组件得到的context
const MyContext = React.createContext()
// 在引入后代组件的地方用标签包裹
// 传递的值必须以value作为属性名
```
```js
// 子组件声明接收context
static contextType = MyContext
// 传递的值都在this.context中
this.context
```
2. 两种组件通用接收context方法
```js
{
value => {
console.log(value)
}
}
```
- 跨文件间传递context,需要新建一个 `JS` 文件用于暴露一个 `React.createContext()`,然后分别在祖先组件和后代组件中引入即可
- `PureComponent`:组件优化
```js
import { PureComponent } from 'react'
class Parent extends PureComponent {
render(){
return (
)
}
}
class Child extends PureComponent {
render(){
return (
)
}
}
```
- 继承 `PureComponent` 的组件,内部不能使用 `shouldComponentUpdate` 方法
- `render props`:在未知两个组件父子关系的情况下,传递属性
```js
class App extends Component {
render() {
return (
} />
)
}
}
class A extends Component {
render() {
return (
this is A
{this.props.render({name: 'ZhangSan'})}
)
}
}
class B extends Component {
render() {
return (
this is B, {this.props.name}
)
}
}
```
- 错误边界:只能捕获子组件 **`生命周期`** 中产生的错误,开发环境下,提示错误后会再次报错;生产环境下,会照常提示错误
```js
// 在父组件中定义一个错误状态变量 `hasError`
state = {
hasError: ''
}
// 错误捕获钩子
static getDerivedStateFromError(error) {
console.log(error)
return { hasError: error }
}
// 统计出错,返回给服务器
componentDidCatch() {
console.log('APP子组件出错')
}
// 在渲染子组件的地方添加判断
{this.state.hasError ? 当前网络错误,请稍后再试
: }
```