ReactHooks

124 阅读8分钟

1、主要功能

  1. 对函数型组件进行增强,让函数型组件可以储存状态,可以拥有处理副作用的能力
  2. 让开发者在不使用类组件的情况下,实现相同的功能
  3. 副作用:只要不是将数据转换为视图的代码都是副作用代码,比如:获取DOM元素、为DOM元素添加事件、设置定时器、发送ajax请求。在类组件中我们通常使用生命周期函数处理这些副作用,在函数组件中我们通过Hooks处理这些事情。

2、类组件的不足

  1. 缺少逻辑复用机制 类组件中一般通过渲染属性和高阶组件(HOC)复用一些逻辑,需要在外层嵌套组件,导致层级比较深。
// hoc的定义
function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: selectData(DataSource, props)
      };
    }
    // 一些通用的逻辑处理
    render() {
      // ... 并使用新数据渲染被包装的组件!
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };

// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id));
 【来源:https://wuhou.fun/149.html,转载请注明】

总结为:为了复用逻辑增加了无实际渲染效果的组件,增加了组件的层级显示十分臃肿,增加了调试的难度以及运行的效率。

  1. 类组件经常会变得很复杂难以维护 将一组相干的业务逻辑拆分到多个生命周期函数中,在一个生命周期函数内存在多个不相干的业务逻辑。

  2. 类成员方法不能保证this指向的正确性(通常需要在事件处理函数中改变this的指向)

3、使用useState让函数组件保存状态

  1. 通常一个函数中的变量,在函数执行完后,变量的内存会被释放掉。因此函数型组件原本是不可以保存数据的。但使用useState就可以让函数组件保存状态,useState内部是使用闭包保存状态的。当状态值改变后函数组件会重新渲染。 image.png

  2. useState的使用细节

  • 接收唯一的参数即状态初始值. 初始值可以是任意数据类型.
  • 返回值为数组. 数组中存储状态值和更改状态值的方法. 方法名称约定以set开头, 后面加上状态名称.
  • 方法可以被调用多次. 用以保存不同状态值.
  • 参数可以是一个函数, 函数返回什么, 初始状态就是什么, 函数只会被调用一次, 用在初始值是动态值的情况 image.png
  1. 设置状态方法的使用细节
  • 设置状态值方法的参数可以是一个值也可以是一个函数
  • 设置状态值方法的方法本身是异步image.png
  1. 实现原理
  • 由于useState可以被不确定的多次调用,因此需要使用数组储存state变量和设置state的函数数组setters
  • 各个useState方法需要有自己的下标,因此需要用闭包储存对应的下标
  • 由于setState方法会导致组重新渲染,因此需要在重新渲染方法render中重置state
let state = [];
let setters = [];
let stateIndex = 0;

// 使用闭包储存数组下标,便于点击设置state时拿到正确的下标值
function createSetter (index) {
  return function (newState) {
    state[index] = newState;
    render ();
  }
}

function useState (initialState) {
  state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
  setters.push(createSetter(stateIndex));
  let value = state[stateIndex]; // 拿到状态值
  let setter = setters[stateIndex];
  stateIndex++;
  return [value, setter];
}

function render () {
  stateIndex = 0; // 重置state,避免页面重新渲染后下标累加
  ReactDOM.render(<App />, document.getElementById('root'));
}

image.png

4、useReducer() 另一种让函数组件保存状态的方式(类似多个setState)

image.png

useReducer相对useState函数的优势:
App组件的子组件需要修改count,则只需将dispatch传递给子组件去调用action

  • 实现原理(基于useState):实际就是加了reducer函数去判断各种action,触发actiondispathch
function useReducer (reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch (action) {
    const newState = reducer(state, action);
    setState(newState);
  }
  return [state, dispatch];
}

function App() {
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return <div>
    {count}
    <button onClick={() => dispatch({type: 'increment'})}>+1</button>
    <button onClick={() => dispatch({type: 'decrement'})}>-1</button>
  </div>;

5、useContext 用在跨组件层级获取数据时简化获取数据的代码

image.png

6、useEffect 让函数型组件拥有处理副作用的能力. 模拟生命周期函数

  1. 执行时机分析
  • 可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合
  • useEffect(() => {}) => componentDidMount, componentDidUpdate
  • useEffect(() => {}, []) => componentDidMount
  • useEffect(() => () => {}) => componentWillUnMount

image.png

  1. 使用方式
  • 为window对象添加滚动事件
  • 设置定时器让count数值每隔一秒增加1

image.png

  • useEffect 钩子函数的第二个参数--只有指定数据发生变化时触发effect

image.png

  1. useEffect 解决的问题
  • 按照用途将代码进行分类 (将一组相干的业务逻辑归置到了同一个副作用函数中)
  • 简化重复代码, 使组件内部代码更加清晰
  1. 结合异步函数
  • useEffect中的参数函数不能是异步函数, 因为useEffect函数要返回清理资源的函数, 如果是异步函数就变成了返回Promise
  • Understanding React’s useEffect cleanup function
useEffect(() => {
    effect
    return () => {
        cleanup
    }
}, [input])

image.png

  1. 实现原理
  • useEffect同样可以多次调用
function render () {
  stateIndex = 0;
  effectIndex = 0;
  ReactDOM.render(<App />, document.getElementById('root'));
}

// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;

function useEffect(callback, depsAry) {
  // 判断callback是不是函数
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
  // 判断depsAry有没有被传递
  if (typeof depsAry === 'undefined') {
    // 没有传递
    callback();
  } else {
    // 判断depsAry是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex];
    // 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
    let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
    // 判断值是否有变化
    if (hasChanged) {
      callback();
    }
    // 同步依赖值
    prevDepsAry[effectIndex] = depsAry;
    effectIndex++;
  }
}

image.png

7、useLayoutEffect()

useEffect在浏览器渲染完成之后执行

useLayoutEffect在浏览器渲染之前执行

所以useLayoutEffect总是比useEffect先执行

useLayoutEffect里面的任务最好是影响了布局视图,但如果无此操作,为了用户体验,优先使用useEffect(优先渲染视图)

image-20220213143535492

8、useMemo()

  • useMemo 的行为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值.
  • useMemo 会缓存计算结果. 如果监测值没有发生变化, 即使组件重新渲染, 也不会重新计算. 此行为可以有助于避免在每个渲染上进行昂贵的计算。

image.png

  • 示例: image.png

9、memo()

  • 性能优化, 如果本组件中的数据没有发生变化, 阻止组件更新. 类似类组件中的 PureComponentshouldComponentUpdate(return false组织渲染)

image.png

  • 计数器案例演示:

image.png

10、useCallback()

问题:如8中示例解决了父组件state变化后,子组件虽然没有依赖父组件的state,但是会因父组件重新渲染而渲染的问题。用memo阻止组件的重新渲染。但如果父组件传给子组件方法时,仍会存在因父组件重新渲染,导致重新创建了方法实例。子组件也会跟着渲染。这时,可以使用useCallback()缓存函数,监听setState()方法的变化, 作用:性能优化, 缓存函数, 使组件重新渲染时得到相同的函数实例

image.png

11、useRef()

  • 作用一:获取 DOM 元素

1656213661206.png

  • 作用二: 保存数据(跨组件周期) useRef() VS useState() 对比:useState()保存的是状态数据,当数据变化后,会触发组件的重新渲染。而useRef() 保存的不是状态数据,即使去更改它保存的数据也不会触发组件的重新渲染。即使组件重新渲染, 保存的数据仍然还在。我通常用于保存在程序运行过程中的一些辅助的手续。 image.png

12、forwardRef()

少部分时候我们希望props包含ref,这时候就需要forwardRef

13、自定义Hook

  • 自定义 Hook 是标准的封装和共享逻辑的方式.
  • 自定义 Hook 是一个函数, 其名称以 use 开头.
  • 自定义 Hook 其实就是逻辑和内置 Hook 的组合.

image.png

14、react-router-dom 路由提供的钩子函数使用

image.png

15(副作用Side-effect)

  1. 什么是副作用

与药物的副作用类似: 减肥药(拉肚子),头孢(过敏),泰诺(头痛)

副作用与纯函数相反,指一个函数处理了与返回值无关的事情

输入参数一样,而输出结果不确定的情况就是副作用,比如我们发送请求,就不一定能得到相同返回

副作用不全是坏事,很多代码必须得借助副作用才能实现如AJAX,修改dom,甚至是console log,副作用会给系统添加不可控的因素,但我们也不应该想方设法躲避副作用,更应该避免的是错误的代码逻辑和思维理念

  1. 纯函数( pure function )
  • 给一个函数同样的参数,那么这个函数永远返回同样的值
  • React组件输入相同的参数(props),渲染UI应该永远一样