You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
如果新的 state 需要通过使用先前的 state 计算得出,可以往 setState 传递函数,该函数将接收先前的 state,并返回一个更新后的值
importReact,{useState}from'react'exportdefaultfunctionCounter(){const[count,setCount]=useState(0);constlazyAdd=()=>{setTimeout(()=>{// 每次执行都会最新的state,而不是使用事件触发时的statesetCount(count=>count+1);},3000);}return(<div><p>the count now is {count}</p><buttononClick={()=>setCount(count+1)}>add</button><buttononClick={lazyAdd}>lazyAdd</button></div>);}
惰性初始 state
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只会在初始渲染时被调用
清除函数的执行时机类似于 class 组件componentDidUnmount 生命周期,这的话使用 useEffect 函数可以将组件中互相关联的部分拆分成更小的函数,防止遗忘导致不必要的内存泄漏
importReact,{useState,useEffect}from'react';exportdefaultfunctionCounter(){const[count,setCount]=useState(0);useEffect(()=>{console.log('start an interval timer')consttimer=setInterval(()=>{setCount((count)=>count+1);},1000);// 返回一个清除函数,在组件卸载前和下一个effect执行前执行return()=>{console.log('destroy effect');clearInterval(timer);};},[]);return(<div><p>count now is {count}</p><buttononClick={()=>setCount(count+1)}>+</button></div>);}
importReact,{useState,useEffect}from'react';exportdefaultfunctionCounter(){const[count,setCount]=useState(0);useEffect(()=>{document.title=`You clicked ${count} times`;},[count]);// 仅在 count 更改时更新return(<div><p>count now is {count}</p><buttononClick={()=>setCount(count+1)}>+</button></div>);}
functionButton(){letappContextValue=useContext(AppContext);lettheme=appContextValue.theme;// 获取 theme 属性returnuseMemo(()=>{// The rest of your rendering logicreturn<ExpensiveTreeclassName={theme}/>;},[theme])}
useReducer
useReducer 作为 useState 的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
使用 useRef 创建的 ref 对象可以作为访问 DOM 的方式,将 ref 对象以 <div ref={myRef} /> 形式传入组件,React 会在组件创建完成后会将 ref 对象的 .current 属性设置为相应的 DOM 节点
importReact,{useRef}from'react'exportdefaultfunctionFocusButton(){constinputEl=useRef(null);constonButtonClick=()=>{inputEl.current.focus();};return(<><inputref={inputEl}type="text"/><buttononClick={onButtonClick}>Focus the input</button></>);}
importReact,{useState,useRef,useEffect}from'react';exportdefaultfunctionCounter(){const[count,setCount]=useState(0);constcurrentCount=useRef();// 使用 useEffect 获取当前 countuseEffect(()=>{currentCount.current=count;},[count]);constalertCount=()=>{setTimeout(()=>{alert(`Current count is: ${currentCount.current}, Real count is: ${count}`);},3000);}return(<><p>count: {count}</p><buttononClick={()=>setCount(count+1)}>Count add</button><buttononClick={alertCount}>Alert current Count</button></>);}
importReactfrom'react';import{useMouse}from'../hooks/useMouse';exportdefaultfunctionMouseMove(){const{ x, y }=useMouse();return(<><p>Move mouse to see changes</p><p>x position: {x}</p><p>y position: {y}</p></>);}
每次使用自定义 Hook 时,React 都会执行该函数来获取独立的 state 和执行独立的副作用函数,所有 state 和副作用都是完全隔离的
React Hooks
Hook 是什么
Hook 是 React 团队在 React 16.8 版本中提出的新特性,在遵循函数式组件的前提下,为已知的 React 概念提供了更直接的 API:props,state,context,refs 以及声明周期,目的在于解决常年以来在 class 组件中存在的各种问题,实现更高效的编写 react 组件
class 组件的不足
难以复用组件间状态逻辑:组件状态逻辑的复用,需要 props render和高阶组件等解决方案,但是此类解决方案的抽象封装将会导致层级冗余,形成“嵌套地狱”
难以维护复杂组件:
this 指向问题:在 JavaScript 中,class 的方法默认不会绑定 this,当调用 class 的方法时 this 的值为 undefined,为了在方法中访问 this 则必须在构造器中绑定或使用 class fields 语法(实验性语法)
难以对 class 进行编译优化:由于 JavaScript 历史设计原因,使用 class 组件会让组件预编译过程中变得难以进行优化,如 class 不能很好的压缩,并且会使热重载出现不稳定的情况
Hook 的优势
Hook 使用规则
Hook 就是 Javascript 函数,使用它们时有两个额外的规则:
在组件中 React 是通过判断 Hook 调用的顺序来判断某个 state 对应的
useState
的,所以必须保证 Hook 的调用顺序在多次渲染之间保持一致,React 才能正确地将内部 state 和对应的 Hook 进行关联useState
useState
用于在函数组件中调用给组件添加一些内部状态 state,正常情况下纯函数不能存在状态副作用,通过调用该 Hook 函数可以给函数组件注入状态 stateuseState
唯一的参数就是初始 state,会返回当前状态和一个状态更新函数,并且useState
返回的状态更新函数不会把新的 state 和旧的 state 进行合并,如需合并可使用 ES6 的对象结构语法进行手动合并方法使用
等价 class 示例
useState
返回的状态类似于 class 组件在构造函数中定义this.state
,返回的状态更新函数类似于 class 组件的this.setState
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,可以往
setState
传递函数,该函数将接收先前的 state,并返回一个更新后的值惰性初始 state
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只会在初始渲染时被调用
跳过 state 更新
调用 State Hook 的更新函数时,React 将使用
Object.is
来比较前后两次 state,如果返回结果为 true,React 将跳过子组件的渲染及 effect 的执行useEffect
在函数组件主体内(React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
useEffect
Hook 的使用则是用于完成此类副作用操作。useEffect
接收一个包含命令式、且可能有副作用代码的函数useEffect
函数会在浏览器完成布局和绘制之后,下一次重新渲染之前执行,保证不会阻塞浏览器对屏幕的更新方法使用
等价 class 示例
useEffect
Hook 函数执行时机类似于 class 组件的componentDidMount
、componentDidUpdate
生命周期,不同的是传给useEffect
的函数会在浏览器完成布局和绘制之后进行异步执行清除 effect
通常情况下,组件卸载时需要清除 effect 创建的副作用操作,
useEffect
Hook 函数可以返回一个清除函数,清除函数会在组件卸载前执行。组件在多次渲染中都会在执行下一个 effect 之前,执行该函数进行清除上一个 effect清除函数的执行时机类似于 class 组件
componentDidUnmount
生命周期,这的话使用useEffect
函数可以将组件中互相关联的部分拆分成更小的函数,防止遗忘导致不必要的内存泄漏优化 effect 执行
默认情况下,effect 会在每一次组件渲染完成后执行。
useEffect
可以接收第二个参数,它是 effect 所依赖的值数组,这样就只有当数组值发生变化才会重新创建订阅。但需要注意的是:useContext
Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法,
useContext
用于函数组件中订阅上层 context 的变更,可以获取上层 context 传递的value
prop 值useContext
接收一个 context 对象(React.createContext
的返回值)并返回 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的<MyContext.Provider>
的value
prop 决定方法使用
等价 class 示例
useContext(MyContext)
相当于 class 组件中的static contextType = MyContext
或者<MyContext.Consumer>
useContext
并没有改变消费 context 的方式,它只为我们提供了一种额外的、更漂亮的、更漂亮的方法来消费上层 context。在将其应用于使用多 context 的组件时将会非常有用优化消费 context 组件
调用了
useContext
的组件都会在 context 值变化时重新渲染,为了减少重新渲染组件的较大开销,可以通过使用 memoization 来优化假设由于某种原因,您有
AppContext
,其值具有theme
属性,并且您只想在appContextValue.theme
更改上重新渲染一些ExpensiveTree
React.memo
useMemo
的组件useReducer
useReducer
作为useState
的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。使用
useReducer
还能给那些会触发深更新的组件做性能优化,因为父组件可以向自组件传递 dispatch 而不是回调函数方法使用
初始化 state
useReducer 初始化 sate 的方式有两种
useRef
useRef
用于返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数(initialValue
)useRef
创建的 ref 对象就是一个普通的 JavaScript 对象,而useRef()
和自建一个{current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象绑定 DOM 元素
使用
useRef
创建的 ref 对象可以作为访问 DOM 的方式,将 ref 对象以<div ref={myRef} />
形式传入组件,React 会在组件创建完成后会将 ref 对象的.current
属性设置为相应的 DOM 节点绑定可变值
useRef
创建的 ref 对象同时可以用于绑定任何可变值,通过手动给该对象的.current
属性设置对应的值即可性能优化(useCallback & useMemo)
useCallback
和useMemo
结合React.Memo
方法的使用是常见的性能优化方式,可以避免由于父组件状态变更导致不必要的子组件进行重新渲染useCallback
useCallback
用于创建返回一个回调函数,该回调函数只会在某个依赖项发生改变时才会更新,可以把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染的子组件,在 props 属性相同情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果useCallback(fn, deps)
相当于useMemo(() => fn, deps)
,以上useCallback
可替换成useMemo
结果如下:useMemo
把“创建”函数和依赖项数组作为参数传入
useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算使用注意:
useMemo
的函数会在渲染期间执行,不要在这个函数内部执行与渲染无关的操作useMemo
在每次渲染时都会计算新的值其他 Hook
useImperativeHandle
useImperativeHandle
可以让你在使用ref
时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle
应当与React.forwardRef
一起使用:useLayoutEffect
useLayoutEffect
与useEffect
类似,与useEffect
在浏览器 layout 和 painting 完成后异步执行 effect 不同的是,它会在浏览器布局 layout 之后,painting 之前同步执行 effectuseLayoutEffect
的执行时机对比如下:自定义Hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中,在 Hook 特性之前,React 中有两种流行的方式来共享组件之间的状态逻辑:render props和高阶组件,但此类解决方案会导致组件树的层级冗余等问题。而自定义 Hook 的使用可以很好的解决此类问题
创建自定义 Hook
自定义 Hook 是一个函数,其名称以 “
use
” 开头,函数内部可以调用其他的 Hook。以下就是实时获取鼠标位置的自定义 Hook 实现:使用自定义 Hook
自定义 Hook 的使用规则与 Hook 使用规则基本一致,以下是
useMouse
自定义 Hook 的使用过程:每次使用自定义 Hook 时,React 都会执行该函数来获取独立的 state 和执行独立的副作用函数,所有 state 和副作用都是完全隔离的
参考文献
React Hooks 官方文档
详解 React useCallback & useMemo
Preventing rerenders with React.memo and useContext hook
MutationObserver MDN
useLayoutEffect和useEffect的区别
The text was updated successfully, but these errors were encountered: