React-进阶知识

2021/1/10 React
// Redux
1. 相当于一个公共的存储区域和 vuex 类似
2. Redux = Reducer + Flux
3. 工作流程
  1. React Components 通过 dispatch(action)调用 Action Creators
  2. Action Creators 会通知到 Store
  3. Store 接收到 Action Creators 的两个参数即 previousState 和 action,会把这两个参数传递给 Reducers 中去操作
  4. Reducers 在创建 Store 时传递给了 Store
  5. Reducers 在操作完成后把对应的 newState 返回给 Store
  6. React Components 监听到 store 的变化重新获取到 Store 中的 value
  7. 相当于学生到图书馆借书流程
    1. React Components 相当于学生
    2. Store 相当于图书馆管理员
    3. Reducers 相当于书本具体位置
    4. Action Creators 相当于学生向管理员借书的对话语句
4. 常用 API
  1. createStore
  2. store.dispatch
  3. store.getState
  4. store.subscribe
5. 示例
  // index.js
  import { createStore } from 'redux' // npm install redux
  import reducer from './reducer.js'
  const store = createStore(reducer)
  export default store
  // reducer.js
  import { INPUTCHANGE, ADDITEM } from './actionType.js'
  const defaultState = {
    value: '',
    list: []
  }
  export default (state = defaultState, action) => {
    if (action.type === INPUTCHANGE) {
      // reducer 可以接收 state,但是绝不能修改 state
      const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
      newState.value = action.value
      return newState
    }
    if (action.type === ADDITEM) {
      // reducer 可以接收 state,但是绝不能修改 state
      const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
      newState.list.push(state.value)
      newState.value = ''
      return newState
    }
    return state
  }
  // actionType.js
  export const INPUTCHANGE = 'inputChange'
  export const ADDITEM = 'addItem'
  // actionCreators.js
  import { INPUTCHANGE, ADDITEM } from './actionType.js'
  export const handleInputChangeAction = value => ({
    type: INPUTCHANGE,
    value
  })
  export const handleAddItemAction = () => ({
    type: ADDITEM
  })
  // 组件
  import React, { Component } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
  import store from './index.js'
  import { handleInputChangeAction, handleAddItemAction } from './actionCreators.js'
  import { INPUTCHANGE, ADDITEM } from './actionType.js'
  class App extends Component {
    constructor(props) {
      super(props)
      this.state = store.getState() // getState 方法是 store 自带的
      this.handleChange = this.handleChange.bind(this)
      this.handleAdd = this.handleAdd.bind(this)
      store.subscribe(this.handleStoreChange) // 订阅或监听 store 变化
    }
    render() {
      return (
        <div>
          {/* value 是 store 中存储的 */}
          <input value = { this.state.value } onChange = { this.handleChange } />
          <button onClick = { this.handleAdd }>添加</button>
        </div>
      )
    }
    handleStoreChange() {
      // 一旦 store 发生改变就重新获取 store 里的数据
      this.setState(store.getState()) // store.getState() 返回一个对象
    }
    handleChange(e) {
      const action = {
        // action 下必须有一个 type 字段用于给到 reducer 做区分,而其他 value 都可以自定义名称
        type: INPUTCHANGE,
        value: e.target.value
      }
      // 最好集中管理 action 函数,而不是像上面一个在组件中创建 action
      const action = handleInputChangeAction(e.target.value)
      store.dispatch(action)
    }
    handleAdd() {
      const action = {
        type: ADDITEM
      }
      // 最好集中管理 action 函数,而不是像上面一个在组件中创建 action
      const action = handleAddItemAction()
      store.dispatch(action)
    }
  }
export default App
----------------------------------------------------------------------------------------------
// React-Redux 的使用
1. npm install react-redux
2. class组件示例
  // index.js
  import React from 'react'
  import ReactDOM from 'react-dom'
  import TodoList from './TodoList'
  import { Provider } from 'react-redux'
  import store from './store' // store 和正常定义的一样

  // 通过 Provider 让在里面的每个组件都能使用 store,还需要组件做连接
  const App = (
    <Provider store = { store }>
      <TodoList/>
    </Provider>
  )

  ReactDOM.render(App, document.getElementById('root'))
  // TodoList.js
  import React, { Component } from 'react'
  import { connect } from 'react-redux'

  class TodoList extends Component {
    render() {
      const {
        inputValue,
        handleChange,
        handleClick
      } = this.props
      return (
        <div>
          <input value = { inputValue } onChange = { handleChange } />
          <botton onClick = { handleClick }>提交</button>
        </div>
      )
    }
  }

  // 通过 react-redux 中的 connect 方法把当前组件和 store 做连接
  // mapStateToProps,会把 store 中的数据映射到组件的 props 中
  // mapDispatchToProps 会把 store 的 dispatch 方法映射到组件的 props 中
  const mapStateToProps = state => {
    return {
      inputValue: state.inputValue
    }
  }
  const mapDispatchToProps = dispatch => {
    return {
      handleChange(e) {
        const action = {
          type: 'change',
          value: e.target.value
        }
        dispatch(action)
      },
      handleClick() {
        const action = {
          type: 'add',
          value: e.target.value
        }
        dispatch(action)
      }
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(TodoList)
3. functioncomponent 组件使用 useSelector 获取数据和 useDispatch 分发 action
  import { useSelector, useDispatch } from 'react-redux'
  const num1 = useSelector(state => state.num1)
  const num2 = useSelector(state => state.num2)
  const useDispatch = useDispatch()
  useDispatch(action)
----------------------------------------------------------------------------------------------
// redux chrome 扩展
1. 在网上应用商店安装完扩展后,需要在创建 store 的时候传入第二个参数即
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
2. 示例
  // store.js
  import { createStore } from 'redux' // npm install redux
  import reducer from './reducer.js'

  // 使用 devTools
  const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__())
  export default store
----------------------------------------------------------------------------------------------
// Redux 设计和使用的三项原则
1. store 是唯一的
2. 只有 store 能够改变自己的内容 // reducer 只是返回了 newState
3. reducer 必须是纯函数 // 纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用(就是不能对参数做修改)
----------------------------------------------------------------------------------------------
// redux-thunk 中间件
1. 中间件是 action 和 store 之间
2. redux 的中间件就是对 action 的 dispatch 的封装,action 如果返回的是一个对象会直接给到 store,如果返回的是一个函数就会给到 dispatch
3. redux-thunk 是 redux 的中间件,它使得 action 可以是一个函数
4. 可以在 action 中做异步操作,而不是在组件的生命周期中
5. 示例
  // store.js
  import { createStore, applyMiddleware, compose } from 'redux' // npm install redux
  import thunk from 'redux-thunk' // npm install redux-thunk
  import reducer from './reducer.js'

  // 使用 thunk 和 devTools
  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : compose
  const enhancer = composeEnhancers(applyMiddleware(thunk))

  const store = createStore(reducer, enhancer)
  export default store
  // action.js
  // 本来是 return 一个对象,现在 return 一个函数
  // 这个函数接收一个参数是 dispatch
  // 组件可以直接调用这个 action,这个 action 就会去获取数据调用下一个 action
  export const getList = () => {
    return dispatch => {
      axios.get('url').then(res => {
        const action = {
          type: 'getList',
          data: res.data
        }
        dispatch(action)
      })
    }
  }
  // 组件
  componentDidMount() {
    const action = getList()
    store.dispatch(action)
  }
----------------------------------------------------------------------------------------------
// redux-saga 中间件
1. 和 redux-thunk 一样做异步
2. 示例
  // store.js
  import { createStore, applyMiddleware, compose } from 'redux' // npm install redux
  import createSagaMiddleware from 'redux-saga' // npm install redux-saga
  import sagas from './saga.js' // 集中管理的文件
  import reducer from './reducer.js'

  // 使用 saga 和 devTools
  const sagaMiddleware = createSagaMiddleware()
  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : compose
  const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware))

  const store = createStore(reducer, enhancer)
  sagaMiddleware.run(sagas)
  export default store
  // saga.js
  import { takeEvery, put } from 'redux-saga/effects'
  function* getList() {
    const res = yield axios.get('url')
    const action = {
      type: 'initList',
      list: res.data
    }
    yield put(action) // put 方法是 saga 中自带的,相当于 dispatch
  }
  function* sagas() {
    yield takeEvery('getList', getList) // 监听这个 getList 的 action
  }
  export default sagas
  // 组件
  componentDidMount() {
    const action = getList()
    store.dispatch(action)
  }
----------------------------------------------------------------------------------------------
// redux 的 reducer 整合
1. 随着模块的增加,reducer 文件会越来越庞大,通过 redux 下的 combineReducers 可以整合小的 reducer
// reducer.js
import { combineReducers } from 'redux'
import headerReducers from './header/store/reducers.js'
const reducer = combineReducers({
  header: headerReducers
})
export default reducer
2. 通过在各个组件下新建 store 目录,再创建 reducer 文件,在每个组件中管理自己的 reducer
3. 各个组件中的 reducer 文件和未拆分 reducer 时的写法一样
4. 最后通过 store 整合成一个大的 reducer,在使用时会多一层级,store.getState().header.value // 多了整合时起的 header
----------------------------------------------------------------------------------------------
// redux 的 reducer 通过 immutable 库管理
1. 我们都知道,在 reducer 操作 state 时,是不能修改到 state 的,只能返回一个新的 state
2. 为了避免我们的误操作,可以引入 immutable 来管理我们的 state
3. 示例
// reducer.js
  import { INPUTCHANGE } from './actionType.js'
  import { formJS } from 'immutable' // npm install immutable

  const defaultState = formJS({ // 返回一个 immutable 对象
    value: ''
  })
  export default (state = defaultState, action) => {
    if (action.type === INPUTCHANGE) {
      // reducer 可以接收 state,但是绝不能修改 state
      const newState = JSON.parse(JSON.stringify(state)) // 深拷贝
      newState.value = action.value
      return newState
      // 上面的方式是通过深拷贝返回了一个新的 state,而通过 immutable 可以通过以下方式
      // immutable 对象的 set 方法,会结合之前 immutable 对象的值和设置的值,返回一个全新的对象
      return state.set('value', action.value)
    }
    return state
  }
  // 组件,如果使用了 immutable,就不能再通过 state.inputValue 方式取值,需要通过 get 方法
  const mapStateToProps = state => {
    return {
      inputValue: state.get('inputValue')
    }
  }
4. 通过 immutable 来管理我们根目录的 reducer(这样在获取 state 时,就只能通过 get 方式去获取,而不是点的方式)
5. 在获取修改 state 就可以直接都使用 immutable 下的方法了,不然各个组件下的 state 调用方法就会变成以下
    state.header.get('inputValue') // 先使用了 js 的调用方法调用 header,再使用 immutable 的 get 方法
    state.get('header').get('inputValue') // 都使用了 immutable 下的方法
    state.getIn(['header', 'inputValue']) // 和上同一效果
6. 示例
  // reducer.js
  // import { combineReducers } from 'redux' // 变成 redux-immutable
  import { combineReducers } from 'redux-immutable' // npm install redux-immutable
  import headerReducers from './header/store/reducers.js'
  const reducer = combineReducers({
    header: headerReducers
  })
  export default reducer
7. 注意:在 state 中的数据使用了 formJS 进行了转换,而在改变时,action 传过来的数据也需要通过 formJS 进行转换!!!
8. formJS() 对应的转换方法是 toJS() // list.toJS()
9. 多次改变 state 数据的 set 操作
    state.set('value', action.value).set('list', [])
    state.merge({ // 使用 merge 方法替换 set 方法
      value: action.value,
      list: []
    })
----------------------------------------------------------------------------------------------
// 实践
1. 使用 styled-components 写样式 // npm install styled-components
  import { injectGlobal } from 'styled-components'
  // 写全局样式必须跟 `` 反引号
  injectGlobal`
    html {
      margin: 0;
      padding: 0;
    }
  `
  // 组件样式
  import styled from 'styled-components'
  import logo from '../static/logo.png'
  export const Header = styled.input.attrs({
    placeholder: '搜索' // 也可以写在 JSX 中
  })`
    width: 100px;
    height: 100px;
    color: red;
    &.class-name { // 当这个组件上有这个类名时,这个组件会有下面的样式
      color: green;
    }
    .iconfot { // 这个组件下有这个类名时,会修饰这个类对应的样式
      width: 50px;
      height: 50px;
    }
  `
  export const Content = styled.div`
    width: 100px;
    height: 100px;
    background: url(${logo}) // 本地的图片
    background: url(${props => props.imgUrl}) // 组件上传进来的图片
  `
  // 组件引用
  import React, { Component } from 'react'
  import { Header, Content } from 'style.js' // 就是上面的 styled-components 的 js 文件

  class TodoList extends Component {
    render() {
      return (
        <div>
          <Header />
          <Content />
        </div>
      )
    }
  }

  export default TodoList
2. 在使用 styled-components 写组件时如果需要使用 ref 则需要把 ref 改成 innerRef,否则拿不到真实的 DOM
----------------------------------------------------------------------------------------------
// react-router-dom 路由
1. 示例
  // App.js
  import React from 'react'
  import ReactDOM from 'react-dom'
  import TodoList from './TodoList'
  import {
    BrowserRouter,
    Route,
    Switch
    Link,
    useParams, // 在孙子级的组件中使用,子级路由可以直接用 props
    useHistory, // 在孙子级的组件中使用,子级路由可以直接用 props
    useLocation, // 在孙子级的组件中使用,子级路由可以直接用 props
    useRouteMatch // 在孙子级的组件中使用,子级路由可以直接用 props
  } from 'react-router-dom' // npm install react-router-dom
  import Home from './pages/home'
  import Detail from './pages/detail'
  import { Provider } from 'react-redux'
  import store from './store' // store 和正常定义的一样

  // 通过 Provider 让在里面的每个组件都能使用 store,还需要组件做连接
  // 实际没有 `` 反引号,只是编辑器显示效果
  const App = (
    `<Provider store = { store }>
      <div>
        <BrowserRouter>
          <Switch>
            {/* Link 组件和 vue 的 router-link 类似 */}
            <Link to='/'>
              <TodoList/>
            </Link>
            {/* exact 表示只有 url 和 path 内容一样时才显示 */}
            <Route path='/' exact render={() => <div>home</div>}>
            <Route path='/detail' exact render={() => <div>detail</div>}>
            {/* 上面是匹配到路由渲染,下面是匹配到路由显示组件 */}
            <Route path='/' exact component={Home}>
            <Route path='/detail' exact component={Detail}>
            <Route render={() => 404}>
          </Switch>
        </BrowserRouter>
      </div>
    </Provider>`
  )

  ReactDOM.render(App, document.getElementById('root'))
2. 动态路由
  1. 在 Link 组件的 to 属性上写 to = {`/detail/${id}`} 可以把对应参数传递到跳转的组件上去
  2. 在对应的组件上通过 this.props.match.params.id 获取
3. 带参路由
  1. 在 Link 组件的 to 属性上写 to = {`/detail?id=${id}`} 可以把对应参数传递到跳转的组件上去
  2. 在对应的组件上通过 this.props.location.search 获取
4. 重定向路由
  import { Redirect } from 'react-router-dom'
  render() {
    return (
      <Redirect to='/' />
    )
  }
----------------------------------------------------------------------------------------------
// 异步组件
1. 示例
  // loadable.js
  import React from 'react'
  import Loadable from 'react-loadable' // npm install react-loadable

  const LoadableComponent = Loadable({
    loader: () => import('./TodoList'), // 动态加载当前目录下的组件文件
    loading() {
      return <div>正在加载</div> // 加载时显示的组件
    }
  })

  export default () => <LoadableComponent />
2. 当使用异步组件时,需要一个 loadable.js 文件做过渡加载,而在 App.js 中引入的文件也是这个过渡文件
3. 当有异步组件需要获取路由上的参数时,这时如果使用了这个过渡文件,则异步组件就拿不到路由上的参数
4. 需要使用 react-router-dom 中的 withRouter 方法
  // TodoList.js
  import React, { Component } from 'react'
  import { connect } from 'react-redux'
  import { withRouter } from 'react-router-dom' // 能让这个组件获取到路由上的参数

  class TodoList extends Component {
    render() {
      const {
        inputValue,
        handleChange,
        handleClick,
      } = this.props
      return (
        <div>
          <input value = { inputValue } onChange = { handleChange } />
          <botton onClick = { handleClick }>提交</button>
        </div>
      )
    }
  }

  // 通过 react-redux 中的 connect 方法把当前组件和 store 做连接
  // mapStateToProps,会把 store 中的数据映射到组件的 props 中
  // mapDispatchToProps 会把 store 的 dispatch 方法映射到组件的 props 中
  const mapStateToProps = state => {
    return {
      inputValue: state.inputValue
    }
  }
  const mapDispatchToProps = dispatch => {
    return {
      handleChange(e) {
        const action = {
          type: 'change',
          value: e.target.value
        }
        dispatch(action)
      },
      handleClick() {
        const action = {
          type: 'add',
          value: e.target.value
        }
        dispatch(action)
      }
    }
  }

  export default connect(mapStateToProps, mapDispatchToProps)(withRouter(TodoList))
5. 使用 React.lazy 和 React.Suspense 做异步组件
  import React from 'react'
  const Content = React.lazy(() => import('./content'))
  class App extends React.Component {
    render() {
      return (
        <React.Suspense fallback = { <div> loding... </div> }>
          <Content />
        </React.Suspense>
      )
    }
  }
----------------------------------------------------------------------------------------------
// React 高阶组件和 Render Props
1. 关于组件公共逻辑的抽离
  1. mixin 已被 react 弃用
  2. 使用高阶组件 HOC // 模式简单,但会增加组件层级
  3. Render Props // 代码简洁,学习成本较高
2. HOC 示例 // 命名规范以 with 开头,如 withAddToCart
  // 类似是工厂模式或装饰器,传入一个组件返回一个新的组件,新的组件包含了公共的逻辑
  // 高阶组件不是一种功能,而是一种模式
  // react-redux 的 connect 就是一个高阶组件
  const HOCFactory = Component => {
    class HOC extends React.Component {
      // 在此定义多个组件的公共逻辑
      // 如获取鼠标的位置
      constructor(props) {
        super(props)
        this.state = {
          x: 0,
          y: 0
        }
        this.handleMouseMove = this.handleMouseMove.bind(this)
      }

      render() {
        // 1. 透传 props
        // 2. 传递公共的参数,如 mouse
        return (
          <div onMouseMove = { this.handleMouseMove }>
            <Component {...this.props} mouse = { this.state } />
          </div>
        )
      }

      handleMouseMove(e) {
        this.setState({
          x: e.clientX,
          y: e.clientY
        })
      }

    }
    return HOC
  }
  // 组件.js
  import HOCFactory from './HOC.js'
  import React from 'react'
  class Test extends React.Component {
    constructor(props) {
      super(props)
    }
    render() {
      return (
        {/* 在当前组件中即可获取到 x、y 的值 */}
        <h1>{ this.props.mouse.x }</h1>
        <h1>{ this.props.mouse.y }</h1>
      )
    }
  }
  export default HOCFactory(Test) // 返回 x、y 的公共逻辑
3. Render Props 示例
  // 传递一个 render 函数给公共组件,公共组件再 return 这个 render 函数,顺便把参数传进去
  // 通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件
  class Mouse extends React.Component {
    // 在此定义多个组件的公共逻辑
    // 如获取鼠标的位置
    constructor(props) {
      super(props)
      this.state = {
        x: 0,
        y: 0
      }
      this.handleMouseMove = this.handleMouseMove.bind(this)
    }

    render() {
      // 1. 透传 props
      // 2. 传递公共的参数,如 mouse
      return (
        <div onMouseMove = { this.handleMouseMove }>
          {/* 将当前 state 作为 props 传递给 render */}
          { this.props.render(this.state) }
        </div>
      )
    }

    handleMouseMove(e) {
      this.setState({
        x: e.clientX,
        y: e.clientY
      })
    }

  }
  Mouse.propType = {
    render: PropType.func.isRequired
  }
  // Test.js
  import React from 'react'
  import Mouse from './Mouse.js'
  class Test extends React.Component {
    constructor(props) {
      super(props)
    }
    render() {
      return (
        {/* 在当前组件中即可获取到 x、y 的值 */}
        <Mouse render = { props => <h1>{props.x} {props.y}</h1> } />
      )
    }
  }
----------------------------------------------------------------------------------------------
// 错误监听
1. getDerivedStateFromError + componentDidCatch
2. getDerivedStateFromError 用于更新 state,使下一次渲染能够显示降级后的 UI
3. componentDidCatch 用于统计上报错误信息
4. 只监听组件渲染时报错,不监听 DOM 事件、异步错误
5. 不会监听 DOM 事件报错,需要 try catch
6. 不会监听异步错误,可用 window.onerror
7. production 环境生效,develop 环境会直接抛出错误
8. promise 未处理的 catch 需要 onunhandledrejection
----------------------------------------------------------------------------------------------
// React Hooks
1. 可选功能,100% 向后兼容,没有破坏性改动,不会取代 'class' 组件,尚无计划要移除 'class' 组件
2. 函数组件的特点
  1. 没有组件实例
  2. 没有生命周期
  3. 没有 state 和 setState,只能接收 props
3. 'class' 组件的问题
  1. 大型组件很难拆分和重构,很难测试(即不易拆分)
  2. 相同业务逻辑,分散到各个方法中,逻辑混乱
  3. 复用逻辑的复杂,如 mixin、HOC、Render Prop
4. React 组件更易用函数表达
  1. React 提倡函数式编程,view = fn(props)
  2. 函数更灵活,更易拆分,更易测试
  3. 但函数组件太简单,需要增强能力 —— Hooks
----------------------------------------------------------------------------------------------
// useState Hook 让函数组件实现 state 和 setState
1. 默认函数组件没有 state
2. 函数组件是一个纯函数,执行完即销毁,无法存储 state
3. 需要 state hook,即把 state 功能'钩'到纯函数中
4. useState Hook 示例 // 用 useState 实现 state 和 setState 功能
  import React, { useState } from 'react'

  function ClickCounter() {
    // 数组的解构
    // useState 就是一个 hook,最基本的一个 hook
    // 传入一个初始值,相当于在 class 组件中的 state 中定义的
    // 名字自定义,方法一般以 set 开头
    const [count, setCount] = useState(0)
    const [name, setName] = useState('chenj')

    function handleClick() {
      setCount(count + 1)
      setName(count + 2021)
    }

    return (
      <div>
        <div>{ name }点击了{ count }</div>
        <button onClick = { handleClick }>点击</button>
      </div>
    )

  }

  export default ClickCounter
----------------------------------------------------------------------------------------------
// useEffect Hook 让函数组件模拟生命周期
1. 默认函数没有生命周期
2. 函数组件是一个纯函数, 执行完即销毁,自己无法实现生命周期
3. 使用 Effect Hook 把生命周期'钩'到纯函数中
4. useEffect 让纯函数有了副作用
  1. 默认情况下,执行纯函数,出入参数,返回结果,无副作用
  2. 所谓副作用,就是对函数之外造成影响,如设置全局定时任务等
  3. 而组件需要副作用,所以需要 useEffect '钩'到纯函数中
5. useEffect Hook 示例
  import React, { useState, useEffect } from 'react'

  function ClickCounter() {
    // 数组的解构
    // useState 就是一个 hook,最基本的一个 hook
    // 传入一个初始值,相当于在 class 组件中的 state 中定义的
    // 名字自定义,方法一般以 set 开头
    const [count, setCount] = useState(0)
    const [name, setName] = useState('chenj')

    // 传 []
    useEffect(() => {
      console.log('componentDidMount')
      // 如果依赖是空,则返回的函数等同于 componentWillUnmount
      return () => {
        console.log('componentWillUnmount')
      }
    }, []) // 这两个生命周期没有依赖

    // 传 state 数据
    useEffect(() => {
      console.log('componentDidUpdate') // count 和 name 发生变化时,也会执行
      console.log('componentDidMount')
      // 如果传了 state 参数,要特别注意,return 的函数不完全等同于 componentWillUnmount
      // 返回的函数会在下一次 Effect 执行之前被执行!!!
      // 即在数据更新后触发执行 useEffect 钩子之前执行!!!
      return () => {
        console.log('componentWillUnmount')
      }
    }, [count, name]) // 只有 count 和 name 发生变化才会触发,也可以只传第一个

    // 不传
    useEffect(() => {
      console.log('componentDidUpdate') // useState 中的数据发生变化时,也会执行
      console.log('componentDidMount')
      // 如果没传第二个参数,要特别注意,return 的函数不完全等同于 componentWillUnmount
      // 返回的函数会在下一次 Effect 执行之前被执行!!!
      // 即在数据更新后触发执行 useEffect 钩子之前执行!!!
      return () => {
        console.log('componentWillUnmount')
      }
    }) // 没传第二个参数,在初始化和数据发生变化时都会触发(不管哪个数据,只要是 useState 生成的)

    function handleClick() {
      setCount(count + 1)
      setName(count + 2021)
    }

    return (
      <div>
        <div>{ name }点击了{ count }</div>
        <button onClick = { handleClick }>点击</button>
      </div>
    )

  }

  export default ClickCounter
----------------------------------------------------------------------------------------------
// useRef Hook
  import React, { useRef, useEffect } from 'react'

    function UseRef() {
      const btnRef = useRef(null)

      // class 组件的写法
      // this.btnRef = React.createRef()

      useEffect(() => {
        console.log(btnRef.current) // DOM 节点
      }, [])

      return (
        <div>
          <button ref = { btnRef }>点击</button>
        </div>
      )

    }

    export default UseRef
----------------------------------------------------------------------------------------------
// useContext Hook
  import React, { useContext } from 'react'
    // 主题颜色
    const themes = {
      light: {
        foreground: '#000',
        background: '#eee'
      },
      dark: {
        foreground: '#fff',
        background: '#222'
      }
    }
    // 创建 Context
    const ThemeContext = React.createContext(themes.light)

    function App() {
      return (
        <ThemeContext.Provider value = { themes.light }>
          <Toolbar />
        </ThemeContext.Provider>
      )
    }

    // 模拟子组件
    function Toolbar() {
      return (
        <ThemeButton />
      )
    }

    // 模拟孙子组件
    function ThemeButton() {
      // 使用 useContext
      const theme = useContext(ThemeContext)
      return (
        <div style = {{ background: theme.background, color: theme.foreground }}>hello</div>
      )
    }

    export default App
----------------------------------------------------------------------------------------------
// useReducer Hook
1. useReducer 和 redux 的区别
  1. useReducer 是 useState 的代替方案,用于 state 复杂变化
  2. useReducer 是单个组件状态管理,组件通讯还需要 props
  3. redux 是全局的状态管理,多组件共享数据
2. 示例
  import React, { useReducer } from 'react'

  const initialState = { count: 0 }

  const reducer = (state, action) => {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 }
      case 'decrement':
        return { count: state.count - 1 }
      default:
        return state
    }
  }

  function App() {
    // 很像 const [count, setCount] = useState(0)
    const [state, dispatch] = useReducer(reducer, initialState)

    return (
      <div>
        count: { state.count }
        <button onClick = { () => dispatch({ type: 'increment' })}>increment</button>
        <button onClick = { () => dispatch({ type: 'decrement' })}>decrement</button>
      </div>
    )
  }

  export default App
----------------------------------------------------------------------------------------------
// useMemo hook 做数据性能优化
1. memo 类似 'class' 的 PureComponent,对 props 进行浅比较,要结合 memo 使用
2. 示例
  import React, { useState, memo, useMemo } from 'react'

  // 子组件
  const Child = memo(props => {
    return (
      <div>
        { props.userInfo.name }{ props.userInfo.age }
      </div>
    )
  })

  // 父组件
  function App() {

    const [count, setCount] = useState(0)
    const [name, setName] = useState('chenj')

    // 用 useMemo 缓存数据,有依赖,依赖变化才会进行重新渲染
    const userInfo = useMemo(() => {
      return { name, age: 20 }
    }, [name])

    return (
      <div>
        count: { state.count }
        <button onClick = { () => setCount(count + 1)}>增加</button>
        <Child userInfo = { userInfo } />
      </div>
    )
  }

  export default App
----------------------------------------------------------------------------------------------
// useCallback hook 做事件性能优化
1. 和 useMemo 类似,一个针对于数据,一个针对于方法,要结合 memo 使用
2. 示例
  import React, { useState, memo, useMemo, useCallback } from 'react'

  // 子组件
  const Child = memo(props => {
    return (
      <div>
        { props.userInfo.name }{ props.userInfo.age }
        <input onChange = { props.handleChange } />
      </div>
    )
  })

  // 父组件
  function App() {

    const [count, setCount] = useState(0)
    const [name, setName] = useState('chenj')

    // 用 useMemo 缓存数据,有依赖,依赖变化才会进行重新渲染
    const userInfo = useMemo(() => {
      return { name, age: 20 }
    }, [name])

    // 用 useCallback 缓存函数,不需要依赖!!!
    const handleChange = useCallback(e => {
      console.log(e)
    }, [])

    return (
      <div>
        count: { state.count }
        <button onClick = { () => setCount(count + 1)}>增加</button>
        <Child userInfo = { userInfo } handleChange = { handleChange }  />
      </div>
    )
  }

  export default App
----------------------------------------------------------------------------------------------
// 自定义 hook
1. 封装通用功能
2. 开发和使用第三方 hooks
3. 自定义 hook 带来了无限的扩展性,解耦代码
4. 本质是一个函数,以 use 开头
5. 内部正常使用 useState、useEffect 获取其他 hooks
6. 自定义返回结果,格式不限
7. 第三方 Hook
  1. 'https://nikgraf.gitgub.io/react-hooks'
  2. 'https://github.com/umijs/hooks'
8. 示例
  import { useState, useEffect } from 'react'
  import axios from 'axios'

  function useAxios(url) {
    const [loading, setLoading] = useState(false)
    const [data, setData] = useState()
    const [error, setError] = useState()

    useEffect(() => {
      setLoading(true)
      axios.get(url)
        .then(res => setData(res))
        .catch(err => setError(err))
        .finally(() => setLoading(false))
    }, [url])

    return [loading, data, error]
  }

  export default useAxios
----------------------------------------------------------------------------------------------
// Hooks 组件逻辑复用,就是自定义 Hook
1. 'class'组件逻辑复用存在的问题
  1. Mixins
    1. 变量作用域来源不清
    2. 属性重名
    3. Mixins 引入过多会导致顺序冲突
  2. 高阶组件 HOC
    1. 组件层级嵌套过多,不易渲染,不易调试
    2. HOC 会劫持 props,必须严格规范,容易出现疏漏
  3. Render Prop
    1. 学习成本高,不易理解
    2. 只能传递纯函数,而默认情况下纯函数功能有限
2. Hooks 做组件逻辑复用的好处
  1. 完全符合 Hooks 原有规则,没有其他要求,易理解记忆
  2. 变量作用域明确
  3. 不会产生组件嵌套
----------------------------------------------------------------------------------------------
// Hooks 注意事项
1. 为什么要使用 Hooks
  1. 完善函数组件的能力,函数更适合 react 组件
  2. 组件逻辑复用,Hooks 表现更好
  3. 'class' 组件正在变的费解,不易拆解、不易测试、逻辑混乱
2. Hooks 命名规范
  1. 规定所有的 Hooks 都以 use 开头,如 useState
  2. 自定义 Hook 也要以 use 开头
  3. 非 Hooks 的地方,尽量不要使用 useXXX 写法
3. Hooks 使用规范
  1. 只能用于 React 函数组件和自定义 Hook 中,其他地方不可以
  2. 只能用于顶层代码,不能在循环、判断中使用 Hooks,也不能提前打断执行
  3. eslint 插件 selint-plugin-react-hooks
4. 为何 Hooks 要依赖于调用顺序?
  // 纯函数不会对其他全局的变量进行修改
  // 函数组件指的是引入 react、函数执行返回 jsx
  1. 函数组件,纯函数,执行完即销毁
  2. 所以无论组件初始化(render)还是组件更新(re-render)
  3. 都会重新执行一次这个函数,获取最新的组件 // 和 class 组件不一样
  4. 无论是 render 还是 re-render,Hooks 调用顺序必须一致
  5. 如果 Hooks 出现在循环、判断里,则无法保证顺序一致
  6. Hooks 严重依赖于调用顺序!!!
5. React Hooks 注意实现
  1. useState 初始化值,只有第一次有效(调用顺序)
    1. 在第一次 render 的时候会初始化 state
    2. 在后面的 re-redner 的时候只会恢复初始化 state 的值,不会再重新设置新的值,只能用 setXXX 修改
    3. 解决方案
      1. 使用自定义变量 // 不推荐,打破了纯函数的规则
      2. 使用 useRef hook // 不止能获取 DOM 元素
  2. useEffect 内部不能修改 state // 依赖是空数组时
    1. 如果第二个参数传入的是个空数组,即模拟的是 DidMount 生命周期,里面的 state 即使在发生变化
       里面不会实时更新
  3. useEffect 可能出现死循环
    1. 闭包
    2. 在第二个参数的数组里面如果传入的是引用类型的数据,即当这个引用类型的数据发生改变的时候
       会重新执行,又因为 react 在内部使用的是 Object.is() 这个方法进行比较是否有改变,当是
       值类型时能进行比较,而如果是引用类型,即使两个传入的数据是一模一样的,它判断后也是 false
       所以如果在数组中传入引用类型的数据,就会导致两个依赖判断一直都是 false,一直重新执行,死循环
  4. hook 依赖需要注意引用类型数据避免无限循环,当非状态且是引用类型的变量放在依赖时就会出现无限循环
6. 什么时候该使用自定义 hook?
  1. 如果一个函数中需要使用 hook 则可以抽象成自定义 hook
  2. 如果一个函数中不需要使用 hook 则不需要抽象成自定义 hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
Last Updated: 2024/7/31 12:57:25
    飘向北方
    那吾克热-NW,尤长靖