hooks

2021/03/15 react

整理一下关于hooks

1、hooks简介

hooks就是react16.8版本后让函数组件具有类组件的能力的东西。函数组件在没hooks之前是没办法保存组件状态,因为函数组件没this。

先来看一下类组件的写法

import React from 'react';

class MyIndex extends React.Component{
  state = {
    index: 0
  }
  render(){
    return <span>{this.state.index}</span>
  }
}

export default MyIndex;

再看一下hooks的写法

import React, {useState, useEffect} from 'react';

function MyIndexFun(){
  // index是存0的变量,setIndex是修改index的方法, 在方法里或者useEffect里面修改
  let [index, setIndex] = useState(0);

  return <span>{index}</span>
}

export default MyIndexFun;

上面两种写法的效果是一样的。

上面代码中的useState, useEffect就是hooks,不同的hooks有不同的作用, 只能在函数最外层调用hook,不要在循环条件判断里或者子函数里调用。

2、useState

useState是用来存数据的hooks,是基于useReducer实现的一个更方便常用的hooks

而且它每次渲染都是有独立的闭包,有独立的props和state,有自己的事件处理函数。

在调用state hooks的的更新函数并传入当前state时,react会跳过子组件的渲染及effect的执行,react使用Object.is来比较state。

useState的用法如上所示

// setData操作是异步的。
// 如果多次提交setData会形成一个循环链表,然后进行循环执行最后提交返回最终值进行渲染
const [data, setData] = useState(data);

data是状态,是存放数据的变量,setData是设置变量内容状态的方法,它有两种使用方式,

setData(0);

setData((d)=>d+1);

第一种使用方式就是简单的set一个值进去,第二种方式惰性初始化state,它接受一个回调函数,回调函数接受一个参数为最新的data值,然后返回一个值作为data的数据。

第二种使用方法可以解决一个闭包产生的问题,问题如下

const [data, setData] = useState(0);

useEffect(()=>{
  const timer = setInterval(()=>{
    // 这个地方拿到的data一直是0, set完的data会一直是1
    setData(data + 1);

    // 解决方案就是使用函数传参,因为函数接受的参数为最新的data的值
    setData(d => d+1);
    return ()=> clearInterval(timer)
  }, 1000)
}, [])

另外惰性初始化参数只会在初始渲染中起作用,后续渲染会被忽略,这种特性可以用在于初始化的值需要计算之类的情况里。

性能优化:

  • 把内联回调函数及依赖项数组作为参数传入useCallback,它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项发生改变的时候才会更新
  • 把创建函数和依赖项数组作为参数传入useMemo,也会当某个依赖项改变时候才会更新,避免每次渲染的时候高性能开销。

核心原理: 定义一个变量,每次去更新那个变量,然后去执行渲染操作。

let lastState = null;

function useState(initState){
  // 第一次进来必然会走 init,如果是函数就执行,如果不是就拉倒
  // 第二次进来lastState已经有值,不会在进行初始化了。
  let state = lastState || (typeof initState === 'function' ? initState() : initState );

  function setState(newState){
    // 如果newState是函数的话还需要另外处理
    // 如果新的和旧的是一样的话就返回 Object.is()
    lastState = newState;
    render(); // react的render方法
  }

  return [state, setState];

}

3、useReducer

在使用useState的时候,值较为复杂的情况下可以使用useReducer,因为如果useState存的值是一个对象的话,对象的属性修改是不能触发更新的。

useReducer用法

function myReducer(state, action){
  switch (action.type) {
    case 'add':
      return state +1 ;
      break;
    default:
      return state;
      break;
  }
}

let [data, dispatchData] = useReducer(myReducer, 0);

dispatchData({type: 'add'});

它的用法和redux类似,通过dispatch一个action去告诉reduce怎么进行处理,通过提前写好的逻辑进行处理,myReducer函数内部获取到的state是最新的,根据action.type就可以进行处理了。

4、useEffect

在函数组件主体内改变DOM,添加订阅,设置定时器,记录日志以及执行其他包含副作用的操作都是不被允许的,因为会产生奇怪的bug并破坏UI的一致性。使用useEffect可以在函数组件中执行副作用操作,组件在渲染以及销毁的时候都会触发useEffect

  • 副作用:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。

使用方法:


useEffect(()=>{
  console.log('这里组件每次渲染前都会执行')
  return console.log('组件卸载了')
})

useEffect(()=>{
  console.log('这里只会在渲染前执行一次,然后多次渲染也不会执行')
  return console.log('页面卸载')
}, [])

useEffect(()=>{
  console.log('初始执行一次,然后只有等data的数变了才会执行')
  return console.log('组件因为data的数变了所以卸载然后要重新渲染了')
}, [data])

useEffect的使用方式及使用方式不同的效果如上打印文字所示,需要注意的是在useEffect里面使用的外面定义的变量都需要注入到useEffect的依赖里面去,也就是接受的第二个参数的数组里面[data, xxx],只有注入了然后依赖发生改变的时候重新执行,才可以获取到最新的依赖项的值。

5、useLayoutEffect

useLayoutEffectuseEffect的区别是:layout会在数据更新到DOM节点的时候,在DOM节点还没有挂载更新的时候执行,而useEffect会等DOM节点更新完成之后再执行。会有一个前后的执行顺序的区别。

根据以上问题所以在使用useLayoutEffect的时候,不应该在它的里面执行耗时的操作,会阻塞DOM的数据挂载及渲染。

6、useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。

<Context.Provider value=>
  <Counter1>
  <Counter2>
  <Counter3>
</Context.Provider>

这样在所有的子组件里面都可以拿到value = {state}的属性。

7、useRef

通过ref可以获取绑定ref的元素的dom对象。

useRef 会在每次渲染时返回同一个 ref 对象。

当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

8、useMemo

当使用函数组件的时候,组件内部有东西被修改,组件会重新渲染,组件内的变量会重新声明,如果我们不想因为组件的重新渲染而重新声明某些变量的时候可以使用useMemo

const config =  useMemo(()=>{
  a: 1,
  b: 2
}, [index])

这样变量config的值只要index的值不发生改变的情况下,组件重新渲染多少次,config都不会改变。

实现核心原理

class PureComponent extends React.Component{
  shouldComponentUpdate(nextProps, nextState){
    // 如果不一样就更新
    return !(Object.is(nextProps, this.props) && Object.is(nextState, this.state))
  }
}


function memo (oldComponent){
  return class extends PureComponent{
    render(){
      return <oldComponents {...oldComponent}></oldComponents>
    }
  }
}

9、useCallback

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

写法和useMemo相同。

10、自定义hook

如果一个函数是以use开头,并且调用了其他hook,我们就称他为一个自定义的hook。

自定义hook是一种复用状态逻辑的方式,他不复用state本身,hook的每次调用都有一个完全独立的state。

Search

    Table of Contents