React Hooks 详解

167 阅读4分钟

React Hooks 详解

HPAD(YNLCKLH3L)(L2WKZ3L.png

useState

  • 使用状态
    const[n,setN] = React.useState(0)
    `const[user,setUser] = React.useState({name:'F'})
  • 注意事项1: 不可局部更新
    如果state是一个对象,能否部分setState?
    答案是不行的 示例代码
    因为setState不会帮我们合并属性
    那么useReducer会合并属性吗? 也不会
  • 注意事项2: 地址要变
    setState(obj)如果obj地址不变,那么React就认为数据没有变化
  • useState接受函数
const [state,setState] = useState(()=>{
   return initialState
})
//该函数返回初始state,且只执行一次
  • setState接受函数
    setN(i=>i+1)
    什么时候用这种方式? 看代码
    如果你能接受这种形式,应该优先使用这种形式

useReducer

  • 用来践行Flux/Redux的思想
    看代码,共分四步走
    一、创建初始值initialState
    二、创建所有操作reducer(state,action)
    三、传给useReducer,得到读和写API
    四、调用写({type:'操作类型'})
    总的来说useReducer是useState的复杂版
    示例代码
  • 一个用useReducer的表单例子
    链接

如何代替Redux

  • 步骤
    一、将数据集中在一个store对象
    二、将所有操作集中在reducer
    三、创建一个Context
    四、创建对数据的读写API
    五、将第四步的内容放到第三步的Context
    六、用Context.Provider将Context提供给所有组件
    七、各个组件用useContext获取读写API
  • 例子
    代码
    如何模块化?其实很简单,代码

useContext

  • 上下文
    全局变量是全局的上下文
    上下文是局部的全局变量
  • 使用方法
    之前的例子已经介绍过了
    一、使用C=createContext(initial)创建上下文
    二、使用<C.provider>圈定作用域
    三、在作用域内使用useContext(C)来使用上下文
    代码示例
  • 不是响应式的
    你在一个模块将C里面的值改变
    另一个模块不会感知到这个变化

useEffect

  • 副作用(API名字叫得不好)
    对环境的改变即为副作用,如修改document.title
    但我们不一定非要把副作用放在useEffect里
    实际上叫做afterRender更好,每次render后运行
  • 用途
    作为componentDidMount使用,[]作第二个参数
    作为componentDidUpdate使用,可指定依赖
    作为componentWillUnmount使用,通过return
    以上三种用途可同时存在
  • 特点
    如果同时存在多个useEffect,会按照出现次序执行

useLayoutEffect

  • 布局副作用(听起来很...)
    useEffect在浏览器渲染完成后执行
    例子
    useLayoutEffec在浏览器渲染前执行
    通过时间点来侧面证明(此处要画图理解)
  • 特点
    useLayoutEffect总是比useEffect先执行
    useLayoutEffect里的任务最好影响了Layout
  • 经验
    为了用户体验,优先使用useEffect (优先渲染)

useMemo

  • 要理解React.useMemo
    需要先讲React.memo
    React默认有多余的render
    讲代码中的Child用React.memo(Child)代替
    如果props不变,就没有必要再次执行一个函数组件
    最终效果:代码

  • 但是
    这玩意有一个bug:代码
    添加了监听函数之后,一秒破功
    因为App运行时会在次执行第12行,生成新的函数
    新旧函数虽然功能一一样,但是地址不一样!
    怎么办?用useMemo:代码

  • 特点
    第一个参数是()=> value,见定义
    第二个参数是依赖[m,n]
    只有当依赖变化时,才会计算出新的value
    如果依赖不变,那么就重用之前的value
    这不就是Vue 2的computed吗?

  • 注意
    如果你的value是个函数,那么你就要写成useMemo(()=> (x) :=> console.1og(x))
    这是一个返回函数的函数
    是不是很难用?于是就有了useCallback

useCallback

  • 用法
    useCallback(x => log(x),[m])等价于
    useMemo(() => x => log(x), [m])

useRef

  • 目的
    如果你需要一个值,在组件不断render时保持不变
    初始化: const count = useRef(0)
    读取:count.current
    为什么需要current?
    为了保证两次useRef是同一个值(只有引用能做到)
    此处需要画图解释

  • 延伸
    看看Vue 3的ref
    初始化: const count = ref(0)
    读取: count.value
    不同点:当count.value变化时,Vue 3会自动render

  • 能做到变化时自动render吗?
    不能!
    为什么不能?因为这不符合React的理念
    React的理念是UI = f(data)
    你如果想要这个功能,完全可以自己加
    监听ref,当ref.current变化时,调用setX即可

  • 不想自己加?
    那你就用Vue3吧,Vue3帮你加好了

forwardRef

  • 讲了useRef就不得不讲一下它了
    代码1: props 无法传递ref属性
    代码2:实现ref的传递
    代码3:两次ref传递得到button的引用
  • useRef
    可以用来引用DOM对象
    也可以用来引用普通对象
  • forwardRef
    由于props不包含ref,所以需要forwardRef
    为什么props不包含ref呢?因为大部分时候不需要

useIMperativeHandle

自定义 Hook

  • 封装数据操作
    简单例子
    贴心例子
  • 分析
    你还可以在自定义Hook里使用Context
    useState只说了不能在if里,没说不能在函数里运行,只要这个函数在函数组件里运行即可

Stale Closure

G6L1ADHUJN12UR8ATIXODGY.png

本文为fjl的原创文章,著作权归本人和饥人谷所有,转载务必注明来源