这是一篇react的hooks教程 另外欢迎大家访问我的博客
react hooks
与 class Component
的区别
写法更加简洁,不再需要写冗长的生命周期函数
class Componet hoc
的阅读让人看起来不易理解, 在组件之间复用状态逻辑很难.
hooks
使用规则
只能在函数最外层 调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在React的函数组件中调用 Hook。不要在其他JavaScript函数中调用。除了自定义的hook以外
react hooks
常见的api使用useState
用法
identity: const [state, setState] = useState(initialState)
useState
的作用是在react函数组件中添加state的hook。
1 2 3 4 5 6 7 8 9 10 import React, { useState } from 'react'; function Count() { /* - count <=> this.state.count, setCount <=> this.setState - setCount 支持两种写法 setCount(count + 1) or setCount(preState => preState + 1) 第一种写法是一种异步机制,会将短时间内的多个setCount合并成一个方法,第二种写法是为了不使用第一种的合并机制。 */ const [count, setCount] = useState(0); return <div onClick={setCount(pre => pre + 1)}>{count}</div> }
useEffect
用法
identity: useEffect(callBack:clearCallBack, [deps])
useEffect
的作用是在函数组件中执行副作用操作, 等价于在ComponetDidMount,ComponentDidUpdate, ComponentWillUnmount 三个函数的组合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import React, { useState, useEffect } from 'react'; function Example() { /* - useEffect接受一个callBack参数和数组参数 - 数组中的值作为依赖,数组中的值发生变化的时候,callBack会重新调用。等价于ComponentDidUpdate - callBack可以return一个clearCallBack,在组件卸载的时候调用clearCallBack。等价于ComponentWillUnmount - useEffect默认会在render流程执行完以后,在调用callBack。等价于ComponetDidMount */ const [isonline, setIsOnline] = useState(false); useEffect(() => { ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange) return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange) } }, [isonline]) }
useContext
用法useContext
的作用是接受一个context对象,并返回该context的当前值。主要用于深层级的组件通讯。需要和React.createContext配合使用 context的值由上层组件中距离当前组件最近的 <MyContext.Provider>的 value prop决定。另外value的值更新会引起调用了uesContext的组件重新渲染。
注意调用useContext的组件即使用了React.memo进行声明,也会重新渲染。因此需要使用memoization 来进行优化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import React, { createContext, Children, useContext, useMemo } from 'react' const GlobalContext = React.createContext() function Child() { const data = useContext(GlobalContext) // memoization 写法 return useMemo(() => { return <div>{data.name}</div> }, [data.name]) } function Parent() { return ( <div> <p>Parent</p> <GlobalContext.Provider value={{name: 'woyao'}}> <Child /> </GlobalContext.Provider> </div> ) }
useRef
用法useRef
返回一个可变的ref对象,ref.current
在组件内是一个全局常量。相当于在组件外写了一个全局常量。也就是说每次重新渲染函数组件时,返回的ref对象都是同一个。 常用于访问Dom ,当做全局变量 。
访问 Dom
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import React, { useRef } from 'react' function Example() { const inputElement = useRef(null) const btnClick = (event) => { inputElement.current.focus() } return ( <React.Fragment> <input ref={inputElement} /> <button ref={btn} onClick={btnClick}/> </React.Fragment> ) }
当做全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import React, { useRef, useEffect, useState } from 'react' function usePrevious(value) { // 每次重新渲染,都会执行useRef, const ref = useRef() /* - 不设置依赖,每次reRender都会重新执行 - 能够返回上一次渲染之前value是什么值 - 注意是先执行return, 在执行useEffect */ useEffect(() => { ref.current = value }) return ref.current } function Counter() { const [count, setCount] = useState(0) const preCount = usePrevious(count) return ( <div> <p>previous: {preCount} </p> <p>now: {count}</p> <button onClick={() => setCount(count + 1)}>click</button> </div> ) }
useImperativeHandle
用法
identity: useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle
可以让你在使用ref
时自定义暴露给父组件的实例值, 与forwardRef
一起使用. 让你能够父组件调用子组件的方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React, { useRef, useImperativeHandle, forwardRef } from 'react' function MyInput(props, ref) { const inputRef = useRef() const childFunc = () => { console.log('hh') } /* ref: ref实例 createHandle: 给ref实例绑上方法 [dps]: 当deps发生变化的时候, createHandle 重新执行 */ useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus() }, childFunc })) return <input ref={inputRef} /> } MyInput = forwardRef(MyInput) function App() { const myInputCoponent = useRef() return ( <> <MyInput ref={myInputCoponent} /> <button onClick={() => { myInputCoponent.current.childFunc() }}> focus now </button> </> ) }
useReducer
用法
identity: const [state, dispatch] = useReducer(reducer, initialArg, init)
useReducer
是useState的替代方案,接受一个形如(state, action) => newState 的 reducer, 并返回 当前的state以及其配套的dispatch方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const initialState = {count: 0}; function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
useMemo
用法
identity: const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
newRenderTemplate = useMemo(() => renderTemplate(), [deps])
useMemo
是减少组件渲染次数,优化组件性能的hook, 传入useMemo
的函数会在渲染期间执行,请不要再函数 内部执行与渲染无关的操作,其实也就是只有依赖项发生变化才会生成新的memoizedValue。这样就减少了不必要的 渲染。一般用在组件中进行解耦操作,与这个逻辑渲染相关的逻辑发生变化就重新渲染,而不相关的就不会重新渲染。 大白话就是有一个count逻辑相关的渲染,还有一个和name相关的逻辑渲染。不要因为name的state属性变化导致 count的渲染函数也重新执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import React, { useState, useMemo } from 'react' const log = console.log.bind(console) function Child(props) { log('child render') const [count, SetCount] = useState(0) const renderCountTemplate = (count) => { console.log('count render') return <div>{count}</div> } return ( <div> child, {props.name} {/* - 使用useMemo防止了不必要的渲染更新,不会因为与当前父组件的props发生变化就会重新对renderCountTemplate进行执行。 */} { useMemo(() => renderCountTemplate(count), [count])} </div> ) } // Child组件优化:组件内 prop,state 的值发生变化才会重新渲染。防止了父组件的更新,子组件也进行不必要的更新 Child = React.memo(Child) function App() { log('parent render') const [name, SetName] = useState('') return ( <> <div>parent, {name}</div> <input onChange={(event) => SetName(event.target.value)} /> <Child name={name} /> </> ) }
useCallback
用法
identity: const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
useCallback
是减少组件渲染次数,优化组件性能的hook, 与useMemo
类似。回调函数仅在某个依赖项改变时才会更新 常用在对事件函数中匿名函数的处理。当然能用到useMemo的地方,useCallback也可以用到。
1 2 3 4 5 6 7 8 9 10 import React, { useCallback, useState } from 'react' const [ count, setCount ] = useState(0) function Counter() { <!-- - 防止了每次count发生变化,导致的重新渲染,都需要重新生成一个新的匿名函数 - 也就是说 () => {setCount(count + 1)} 这个匿名函数不需要再从新的内存空间中创建 --> return <div onClick={useCallback(() => setCount(count+1), [])}>{count}</div> }
useLayoutEffect
用法useLayoutEffect
官方解释说它会在所有的DOM变更之后同步调用effect,可能useEffect
是在所有的DOM变更之后异步调用effect吧.
可以使用它来读取 DOM 布局并同步触发重渲染
在浏览器执行绘制之前,useLayoutEffect
内部的更新计划将被同步刷新。
这么说吧,我也不太useLayoutEffect
的区别。有下面一段代码可以参考一下。估计在使用useEffect
的时候带来了页面的抖动问题的时候就使用useLayoutEffect
。网上的解释:layout会在组件树构建完毕或者刷新完毕后同步立刻执行。effect会等其他js代码执行完毕后执行1 2 3 4 5 6 7 8 9 10 11 12 13 14 function App() { const [count, setCount] = useState(0); useEffect(() => { if (count === 0) { const randomNum = 10 + Math.random()*200 setCount(10 + Math.random()*200); } }, [count]); return ( <div onClick={() => setCount(0)}>{count}</div> ); }
效果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function App() { const [count, setCount] = useState(0); useLayoutEffect(() => { if (count === 0) { const randomNum = 10 + Math.random()*200 setCount(10 + Math.random()*200); } }, [count]); return ( <div onClick={() => setCount(0)}>{count}</div> ); }
效果如下:
自定义hook 其实也就是hook mixin, 公共的方法逻辑可以写成自定义hooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 一个ComponentDidUpdate的简易实现 import { useEffect, useRef } from 'react' function useDidUpdate(cb, deps=[]) { const didMount = useRef(false) useEffect(() => { if (!didMount.current) { didMount.current = true return } cb() }, deps) return didMount.current }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // 一个redux的简易实现 import React, { useContext, useReducer } from 'react' const initState = { count: 0 } const Store = React.createContext(initStore) const MapActionReducer = { ['ADD'](state, action) { return {...state, count: action.payload} } } const reducer = (initState, action) => { return MapActionReducer[action.type](initState, action) } function useGlobalReduxHook(Component) { // dispatch触发state发生变化,会重新执行渲染 const [state, dispatch] = useReducer(reducer, Store) return ( <Store.Provider value={state, dispatch} /> <Component /> </Store.Provider> ) }