React:Hooks 详解

597 阅读4分钟

image-20210731010011933.png

useState

使用状态

const[n, setN] = React.useState(0)
const[user, setUser] = React.useState({name:'F'})

注意事项1——不可局部更新

如果state是一个对象,能否部分setState

不可以,示例代码

80.1.1.gif

添加...user,复制之前属性

80.1.2.gif

因为setState不会帮我们合并属性,而且useReducer也不会合并属性

注意事项2——地址不变

setState(obj)如果obj地址不变,那么React就认为数据没有变化

地址不变的示例,name不会改变

useState 接受函数

const [state, setState] = useState(() => ({
    return initialState
}))
// 该函数返回初始值 state,且只执行一次

useState中把初始值写成函数的好处是减少多余的计算过程(函数只有在用到的时候才会执行)

setState 接受函数

setN(i => i + 1)

代码示例

useReducer

useReducer

用来践行 Flux / Redux 的思想

示例代码

分四步走——

  1. 创建初始值initialState
  2. 创建所有操作reducer(state, action)
  3. 传给useReducer,得到读和写API
  4. 调用({type:'操作类型})

总得来说,useReduceruseState的复杂版

一个用 useReducer 的表单例子

示例代码

代替 Redux

步骤
  1. 将数据集中在一个store对象
  2. 将所有操作集中在reducer
  3. 创建一个Context
  4. 创建对数据的读写API
  5. 将第四步的内容放到第三步的Context
  6. context.ProbiderContext提供给所有组件
  7. 各个组件用useContext获取读写API
例子

示例代码

进行模块化

useContext

上下文

全局变量是全局的上下文

上下文是局部的全局变量

使用方法

之前例子已经介绍过

  1. 使用C=createContext(initial)创建上下文
  2. 使用<C.provider>圈定作用域
  3. 在作用域内使用useContext(C)来使用上下文

代码示例

useContext 注意事项

不是响应式的

在一个模块将C里面的值改变,另一个模块不会感知到这个变化

useEffect & useLayoutEffect

useEffect

副作用

对环境的改变即为副作用,如修改document.title

但不一定非要把副作用放在useEffect

useEffect会在每次render后运行

用途

作为componentDidMount使用,[]作第二个参数

作为componentDidUpdate使用,可指定依赖

代码示例

作为componentWillUnmount使用,通过return

代码示例

以上三种用途可同时存在

特点

如果同时存在多个useEffect,会按照出现次序执行

useLayoutEffect

布局副作用

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

例子

useLayoutEffect在浏览器渲染前执行

image-20210801115439899.png

通过时间点来侧面证明

特点

useLayoutEffect总是比useEffect先执行

useLayoutEffect里的任务最好影响了Layout

经验

为了用户体验,优先使用useEffect(优先渲染)

useMemo

React.memo

React 默认有多余的render

代码中的ChildReact.memo(Child)代替

如果props不变,就没有必要再次执行一个函数组件

最终效果

React.memo的bug

有bug

上面的示例,由于在Child添加了监听函数,App运行时会再次执行第12行,生成新的函数

也就是说,虽然新旧函数功能一样,但是地址不一样

解决办法:用useMemo

useMemo

特点

第一个参数是()=>value,见定义

第二个参数是依赖[m,n]

只有当依赖变化时,才会计算出新的value

如果依赖不变,那么就重用之前的value

这个功能等同于Vue2computed

注意

如果value是个函数,那么就要写成useMemo(()=>(x)=>console.log(x))

这是一个返回函数的函数

语法糖useCallback可以简化

useCallback

useCallback(x => log(x), [m])
// 等价于
useMemo(() => x => log(x), [m])

useRef

目的

如果需要一个值,在组件不断render时保持不变

代码示例

// 初始化
const count = useRef(0)
​
// 读取
count.current

为什么需要current?

为了保证两次useRef是同一个值(只有在引用能做到)

image-20210801195700605.png

延伸

Vue3 的 ref

// 初始化
const count = ref(0)
​
// 读取
count.value

不同点:当count.value变化时,Vue3会自动render

useRef能做到变化时自动render吗?

示例代码

可以看到,对染console.log(count.current)打印出的结果有变化,但是button``update count: 0没有变化。说明useRef不能自动render

80.7.1.gif

不能,不符合 React 理念,React理念是UI=f(data)

如果想要这个功能,可以自己加——

改写代码

监听ref,当ref.current变化时,调用setX即可

forwardRef

代码示例

代码1:props无法传递ref属性

代码2:实现ref的传递

代码3:两次ref传递得到button的引用

useRef

可以用来引用DOM对象

也可以用来引用普通对象

forwardRef

由于props不包含ref,所以需要forwardRef

为什么props不包含ref

因为大部分时候不需要

useImperativeHandle

代码示例

不用useImperativeHandle的代码

用了useImperativeHandle的代码

分析

用于自定义ref的属性

自定义 Hook

封装数据操作

简单例子

贴心例子

分析

还可以在自定义Hook里使用Context

useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可

Stale Closure

过时闭包

参考文章链接

image-20210801180730659.png