React Hook
新呈现背景
类组件的问题
- 复用组件状态难,高阶组件+渲染属性
providers customers
,等一堆工具都是为了解决这个问题,然而造成了很重大的了解老本和组件嵌套天堂 - 生命周期带来的负面影响,逻辑拆分重大
- This 的指向问题
函数组件的局限
- 之前函数组件没有
state
和 生命周期,导致应用场景无限
React Hook
Hooks
是 React 16.8
新增的个性,它能够让你在不编写 class
的状况下应用 state
以及其余的 React
个性,无需转化成类组件
Hook
的应用和实际
useState
和 Hook
的闭包机制
// hook 组件function Counter() { const [count, setCount] = useState(0); const log = () => { setCount(count + 1); setTimeout(() => { console.log(count); }, 3000); }; return ( <div> <p>You clicked {count} times</p> <button onClick={log}>Click me</button> </div> );}// 等效的类组件class Counter extends Component { state = { count: 0 }; log = () => { this.setState({ count: this.state.count + 1, }); setTimeout(() => { console.log(this.state.count); }, 3000); }; render() { return ( <div> <p>You clicked {this.state.count} times</p> <button onClick={this.log}>Click me</button> </div> ); }}
疾速点击下的状况下,想想 Hook
组件和函数式组件控制台打印进去的是什么?
- 类组件打印进去的是
3 3 3
Class
组件的state
是不可变的,通过setState
返回一个新的援用,this.state
指向一个新的援用setTimeout
执行的时候,通过this
获取最新的state
援用,所以这个输入都是3
- 函数组件打印的后果是
0 1 2
函数组件闭包机制,函数组件每一次渲染都有独立的props
和state
每一次渲染都有独立的事件处理函数
每一次渲染的状态不会受到前面事件处理的影响
函数组件渲染拆解
既然每次渲染都是一个独立的闭包,能够尝试代码拆解函数式组件的渲染过程
// 第一次点击function Counter() { const [0, setCount] = useState(0); const log = () => { setCount(0 + 1); // 只能获取这次点击按钮的 state setTimeout(() => { console.log(0); }, 3000); };}// 第二次点击function Counter() { const [1, setCount] = useState(0); const log = () => { setCount(1 + 1); setTimeout(() => { console.log(1); }, 3000); };}// 第三次点击function Counter() { const [2, setCount] = useState(0); const log = () => { setCount(2 + 1); setTimeout(() => { console.log(2); }, 3000); };}
- 三次点击,共
4
次渲染,count
从0
变为3
- 页面第一次渲染,页面看到的
count = 0
- 第一次点击,事件处理器获取的
count = 0
,count
变成1
, 第二次渲染,渲染后页面看到count = 1
,对应上述代码第一次点击 - 第二次点击,事件处理器获取的
count = 1
,count
变成2
, 第三次渲染,渲染后页面看到count = 2
,对应上述代码第二次点击 - 第三次点击,事件处理器获取的
count = 2
,count
变成3
, 第四次渲染,渲染后页面看到count = 3
,对应上述代码第三次点击
让函数式组件也能够输入 3 3 3
有种比较简单并且能解决问题的计划,借用 useRef
useRef
返回一个可变的ref
对象,其current
属性被初始化为传入的参数(initialValue)
useRef
返回的ref
对象在组件的整个生命周期内放弃不变,也就是说每次从新渲染函数组件时,返回的ref
对象都是同一个useRef
能够类比成类组件实例化后的this
,在组件没有销毁的返回的援用都是同一个
function Counter() { const count = useRef(0); const log = () => { count.current++; setTimeout(() => { console.log(count.current); }, 3000); }; return ( <div> <p>You clicked {count.current} times</p> <button onClick={log}>Click me</button> </div> );}
- 这样批改一下,控制台输入的的确是
3 3 3
- ? 既然
Ref
对象整个生命周期都不变,批改current
属性也只是批改属性,那除了打印,这里的You clicked 0 times
,点击三次,会变成3
么? - 显然不能,这个组件没有任何的属性和状态扭转,会从新渲染才怪,所以这里尽管点击了
3
次,然而不会像useState
一样,渲染4
次,这里只会渲染1
次,而后看到的都是You clicked 0 times
- 修复一个问题把另外一个更大的问题引进来,这很程序员。。。
useEffect
通过 useRef
尽管能解决打印的问题,然而页面渲染是不对的,这里还是应用 useState
的计划,配合 useEffect
能够实现咱们想要的成果
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
- 看下
useEffect
的签名,effect
是函数类型,并且必填, 还有第二个可选参数,类型是只读数组 useEffect
是解决副作用的,其执行机会在 每次Render
渲染结束后,换句话说就是每次渲染都会执行,在实在DOM
操作结束后。
配合这个 hook
, 如果每次 state
扭转后渲染完之后,把 ref
外面的值更新,而后控制台打印 ref
的值,参考React实战视频解说:进入学习
function Counter() { const [count, setCount] = useState(0); const currentCount = useRef(count); useEffect(() => { currentCount.current = count; }); const log = () => { setCount(count + 1); setTimeout(() => { console.log(currentCount.current); }, 3000); }; return ( <div> <p>You clicked {count} times</p> <button onClick={log}>Click me</button> </div> );}
这样子写能够合乎咱们的预期成果,页面展现从 0 1 2 3
, 而后控制台输入 3 3 3
,而后咱们拆解下渲染过程。
- 三次点击,共
4
次渲染,count
从0
变为3
- 页面初始化渲染,
count = 0
,currentCount.current = 0
, 页面显示0
, 渲染实现,触发useEffect
,currentCount.current = 0
- 第一次点击,
count = 0
, 渲染实现后,count = 1
, 页面显示1
,触发useEffect
,currentCount.current = 1
- 第二次点击,
count = 1
, 渲染实现后,count = 2
, 页面显示2
,触发useEffect
,currentCount.current = 2
- 第三次点击,
count = 2
, 渲染实现后,count = 3
, 页面显示3
,触发useEffect
,currentCount.current = 3
- 三次点击实现,
currentCount.current = 3
,第四次渲染,页面看到count = 3
,setTimeout
中调用的是currentCount
这个对象,输入都是3
useEffect
的函数返回值
type EffectCallback = () => void | (() => void | undefined);
useEffect
的回调函数能够返回空,也能够返回一个函数,如果返回一个函数的话,在 effect
执行回调函数的时候,会先执行上一次 effect
回调函数返回的函数
useEffect(() => { console.log('after render'); return () => { console.log('last time effect return'); };});
这个 useEffect
,每次渲染完之后,控制台会先输入 last time effect return
,而后再输入 after render
useEffect 和 类组件生命周期
之前提到,useEffct
有两个参数,第二参数是个可选参数,是 effect
的依赖列表, React
依据这些列表的值是否有扭转,决定渲染完之后,是否执行这个副作用的回调
如果不传这个参数,React
会认为这个 effect
每次渲染然之后都要执行,等同于 componentDidUpdate
这个生命周期无约束执行
useEffect(() => { currentCount.current = count;});componentDidUpdate() { currentCount.current = this.state.count;}
如果这个参数是空数组,React
会认为组件内任何状态和属性扭转,都不会触发这个 effect
,相当于这个 effect
是仅仅在组件渲染完之后,执行一次,前面组件任何更新都不会触发这个 effect
,等同 componentDidMount
useEffect(() => { currentCount.current = count;}, []);componentDidMount() { currentCount.current = this.state.count;}
如果配合 useEffect
回调函数的返回函数,能够实现相似 componentWillUnmount
的成果,因为如果是空数组的话,组件任何更新都不会触发 effect
,所以回调函数的返回函数只能在组件销毁的时候执行
useEffect(() => { return () => { console.log('will trigger ar willUnmount') }}, []);componentWillUnmount() { console.log('will trigger ar willUnmount')}
如果依赖列表外面有值,则相似componentDidMount
有条件束缚更新,只有当上一次的状态和这次的不一样,才执行
useEffect(() => { currentCount.current = count;}, [count]);componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { currentCount.current = this.state.count; }}
useEffect
和 闭包问题
假如组件须要在初始化的时候,定义一个定时器,让 count
自增,自然而然的能够写出以下的代码
// 初始化的 count = 0useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000);}, []);componentDidMount() { setInterval(() => { this.setState({ count: this.state.count + 1 }); }, 1000);}
然而理论运行的时候,类组件展现是对的,函数组件从 0
递增到 1
之后,页面渲染就再也不变了
- 之前有提过,类组件因为有
this
这个援用,很容易通过state
拿到最新的值 - 函数组件每次渲染都是独立的闭包,这里因为写的依赖值是
[]
,所以只有首次渲染后,才会这行这个effect
,首次渲染后,count
就是0
,所以setCount(count + 1)
每次都是执行setCount(0 + 1)
,所以定时器工作是失常的,不过取的值有问题。
闭包问题的切入点和产生场景
闭包问题,大多产生在,有些回调函数执行,依赖到组件的某些状态,然而这些状态并没有写到 useEffect
的依赖列表外面。导致执行回调函数的时候,拿到组件的状态不是最新的。
次要的场景有:
- 定时器
- 事件监听的回调
- 各种
Observer
的回调
这些场景,通常只有在组件初始化渲染完之后,定义一次回调函数就好,然而如果回调函数依赖到组件的转态或者属性,这时候就要小心,闭包问题
function Router() { const [state, setState] = useState<string>(''); useEffect(() => { window.addEventListener<'hashchange'>( 'hashchange', () => { // 监听 hash 变动,这里依赖到 state }, false ); }, []);}
例如这里的写法,在组件渲染完监听 hashchange
,回调函数是拿不到后续更新的 state
的,只能能到初始化时候的空字符串。
尝试解决闭包问题-监听state
变动
既然回调函数要每次都拿到最新的 state
,能够监听 state
的变动,state
变动的时候,从新定义事件监听器,改写一下
function Router() { const [state, setState] = useState<string>(''); useEffect(() => { window.addEventListener( 'hashchange', () => { // 监听 hash 变动,这里依赖到 state }, false ); }, [state]);}
以上代码能用,然而 state
每次扭转,就会从新定义一个 hashchange
回调函数,然而上一次的 hashchange
的事件监听器并没有革除,代码能跑,然而内存透露也太重大了,能够配合 useEffect
回调函数返回的函数配合清掉上一次的事件监听器
function Router() { const [state, setState] = useState<string>(''); useEffect(() => { const callback = () => {}; window.addEventListener('hashchange', callback, false); return () => window.removeEventListener('hashchange', callback, false); }, [state]);}
这样内存透露的问题被解决了,然而这种事件监听,失常来说设置一次就好,没必要从新定义,还有别的更好的办法么?
尝试解决闭包问题 - setState
另外一种更新组件状态的形式
useState
返回的更新状态的函数,除了能够传一个值,还能够传一个回调函数,回调函数带一个参数,这个参数是最新的 state
,像这样的话,之前那个定时器的例子,能够批改成这样。
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const id = setInterval(() => { // setCount(count + 1) setCount((c) => c + 1); }, 1000); return () => clearInterval(id); }, []); return <h1>{count}</h1>;}
这里咱们改了一行代码,setCount(count + 1)
改成了 setCount((c) => c + 1)
,这样批改之后,其实定时器回调曾经没有依赖到 count
这个值了,由 setCount
外面的回调函数,返回最新的 count
的值,就是setCount((c) => c + 1)
,外面的 c
.
同样的,对于事件监听器外面,咱们也能够通过这个形式去获取最新的 state
,然而这里有几个问题
- 这个回调函数,其实也只有获取最新的
state
,所以在调用setState
的时候,拿到最新的值的同时,记得把setState
的值,设置成和以后同一个,如果没有返回,那调用setState
之后,state
的值会变成undefined
setState
返回一个同样的值,会不会导致组件和它的子组件从新渲染?找了下文档阐明是这样的:调用 State Hook 的更新函数并传入以后的 state 时,React 将跳过子组件的渲染及 effect 的执行。须要留神的是,React 可能仍须要在跳过渲染前渲染该组件。不过因为 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必放心- 看起来可行的,做一下简略的批改其实能够改成这样
function Router() { const [state, setState] = useState<string>(''); useEffect(() => { const callback = () => { let latestState = ‘’; setState((stateCallback) => { // stateCallback 是最新的 state latestState = stateCallback; // 记得把 stateCallback 返回去,不然 state 会被改成 undefined return stateCallback; }); // latestState 曾经被赋值为最新的 state 了 }; window.addEventListener<'hashchange'>('hashchange', callback, false); }, [])}
这样根本就没问题了,做到了只定义了一次回调,而后也能够获取最新的 state
,两全其美,然而还是有问题的
setState
回调函数如果不写return stateCallback;
这段代码,会导致state
莫名其妙被设置成undefined
,而且十分不好发现,维护性太差setState
是用来扭转组件的state
的,不是让你这样用的的,尽管这样用齐全没问题。然而可维护性太差了,如果你的代码被接手,他人就会纳闷这里为什么要这么写,无正文和变量命名太蹩脚的状况下,代码能够维护性根本为0
- 设置一个同样的
state
,尽管不会导致子组件从新渲染,然而本组件还是有可能从新渲染的,按官网的说法
这个计划不完满。思路再发散一下?执行回调函数的时候,须要获取到最新的 state
,能不能用一个不变的值缓存 state
? 等等?? 不变的值???
解决闭包问题最佳实际-useState
和useRef
useRef
的返回是在整个组件生命周期都是不变的一个对象,能够借助 useRef
来取得最新的 state
。例如这个例子能够改成这样:
function Router() { const [state, setState] = useState<string>(''); const stateRef = useRef<string>(state); // 这样,能够把 stateRef 和最新的 state 绑定起来 stateRef.current = state; // 或者这样,能够把 stateRef 和最新的 state 绑定起来 useEffect(() => { stateRef.current = state; }, [state]); useEffect(() => { const callback = () => { const latestState = stateRef.current; }; window.addEventListener<'hashchange'>('hashchange', callback, false); }, []);}
stateRef.current
下面两种写法,都能够取得最新的 count
,回调函数外面外面间接读取 stateRef.current
的值,能够拿到最新的 state
闭包问题的最优解,节本就是这样了。
useRef
和 useState
的最佳实际
useState
和useRef
认真想想和和类组件的什么属相很类似?是不是和this.state
和this
的属性很像- 在类组件中,如果是不参渲染的属性,间接挂
this
上就好了,如果须要参加渲染的属性,挂在this.state
上 - 同样的,在
Hook
中,useRef
和useState
能够实现相似成果
例如以下的例子
// 函数组件const Child = React.memo(() => { // count 参加页面渲染 const [count, setCount] = useState(0); // userInfo 不参加渲染 const userInfo = useRef(null);});// 类组件class Child extends React.PureComponent { constructor(props) { super(props); // 不参加渲染 this.userInfo = null; // 参加渲染的属性 this.state = { count: 0, }; }}
再看看 useEffect
回调函数的返回值
type EffectCallback = () => (void | (() => void | undefined));return void | (() => void | undefined)
确定是没有返回或者返回一个函数,所以上面这种写法是有问题的,尽管也没有显著表明返回体,就是没有返回一样,然而这个回调函数是异步函数,异步返回默认返回一个 Promise
对象,所以这种写法是不提倡的
const [data, setData] = useState({ hits: [] }); useEffect(async () => { const result = await axios( 'url‘, ); setData(result.data);}, []);
为了躲避这个问题,能够批改一下写法
useEffect(() => { const fetchData = async () => { const result = await axios('url'); setData(result.data); }; fetchData();}, []);
useCallback
把函数写进外面没什么问题,官网也举荐,然而万一我的副作用外面须要解决多个函数或者一个超长的函数的话,一个是不美观,一个是太难保护
这个实用能够利用 useCallback
把函数抽离进来,useCallback
返回一个记忆化的函数,当且仅当依赖列表有任何属性扭转的时候,它才会返回一个新的函数,所以这个个性比拟适宜传给子组件的回调函数
function Counter() { const [count, setCount] = useState(0); const getFetchUrl = useCallback(() => { return 'https://v?query=' + count; }, [count]); useEffect(() => { getFetchUrl(); }, [getFetchUrl]); return <h1>{count}</h1>;}
这里如果 count
扭转的时候,getFetchUrl
的值也会扭转,从而导致 useEffect
也触发
React.memo
React.memo()
返回一个记忆化的值,如果 React 外部会断定,如果从新渲染
props` 不相等,就会从新渲染,如果没有扭转,就不会触发组件渲染
这个个性比拟有用,因为如果父组件从新渲染的时候,子组件就会从新渲染,应用这个个性能够缩小不必要的子组件从新渲染
const Child = memo((props) => { useEffect(() => { }, []) return ( // ... )}, (prevProps, nextProps) => { // 断定相等的逻辑 // 如果某些属性的扭转不须要从新渲染 // 能够编写这个函数})
React.useCallback
和 React.memo
- 为什么讲
useCallback
要把memo
拎进去讲,想一下useCallback
的作用,返回一个缓存的函数,在函数组件外面,每次渲染都会执行一次组件函数,组件函数每次执行,在组件外部的函数都会从新定义,这样的话,父组件传给子组件的回调函数每次渲染都会变 - 再从
memo
的角度去看,父组件每次渲染,子函数组件如果不加memo
的话,就算是子组件无任何依赖,属性都不变的状况下,子组件也会从新渲染 - 如果在父组件独自加为子组件的回调函数增加
useCallback
,这样能够防止回调函数从新定义,然而子组件如果不必memo
包裹,就算任何子组件属性没扭转,还是会导致子组件从新渲染; - 同样的,如果子组件独自用
memo
包裹,父组件每次渲染,从新定义回调函数,还是会导致从新 - 所以,
memo
和useCallback
必须都用上,不然是没用的,不仅达不到优化的成果,而且会减轻 React 比拟的累赘。要不就别用,要不就都用上。
React.useCallback
和 React.memo
最佳实际
父组件用 useCallback
包裹函数,子组件用 memo
包裹组件,要不就都不必
// 子组件// callback 为父组件传过来的回调函数const Child = ({ callback }) => {}// 子组件用 React.memo 包裹export default React.memo(Child);// 父组件const Parent = () => { // 子组件的回调函数用 useCallback 包裹 const callback = React.useCallback(() => {}, []); return <Child callback={callback} />};
Raect.memo
的局限
React.memo
包裹在组件上,能够对传给组件的属性进行断定,父组件导致子组件从新渲染的时候,memo
包裹的组件,会断定属性是否和上次渲染时候否扭转,如果有扭转,子组件从新渲染,否则不会从新渲染。React.memo
有个局限,只能避免来源于内部的属性,如果是来源于外部的属性,React.memo
是无作用的,例如通过useContext
间接注入组件外部的属性,它没法避免,能够看下上面这个简略的例子
export const Store = React.createContext(null);export default function Parent() { const [state, dispatch] = useReducer(reducer, { count: 0, step: 0 }); return ( <Store.Provider value={{ dispatch, state }}> <Step /> <Count /> </Store.Provider> );}export const Count = memo(() => { const { state, dispatch } = useContext(Store); const setCount = () => dispatch({ type: 'changeCount' }); return ( <> <span>count: {state.count}</span> <button onClick={setCount}>change count</button> </> );});export const Step = memo(() => { const { state, dispatch } = useContext(Store); const setCount = () => dispatch({ type: 'changeStep' }); return ( <> <span>count: {state.step}</span> <button onClick={setStep}>change step</button> </> );});
- 下面的组件,
count
或者step
任意这个属性扭转,都会导致两个子组件从新渲染,这显然是不对的。
React.useMemo
代替 React.momo
useMemo
和 memo
一样,返回一个记忆化的值,如果依赖项没有扭转,会返回上一次渲染的后果,它和 useCallback
的差异就在一个是返回记忆化的函数,一个是返回记忆化的值,如果 useMemo
的回调函数执行返回一个函数,那它的成果和 useCallback
是一样的。
因此下面的组件能够改一下,上面这种写法就能够避免任意一个属性扭转会导致两个子组件从新渲染的问题
export const Count = () => { const { state, dispatch } = useContext(Store); const setCount = () => dispatch({ type: 'changeCount' }); return useMemo( () => ( <> <span>count: {state.count}</span> <button onClick={setCount}>change count</button> </> ), [state.count] );};export const Step = () => { const { state, dispatch } = useContext(Store); const setStep = () => dispatch({ type: 'changeStep' }); return useMemo( () => ( <> <span>step: {state.step}</span> <button onClick={setStep}>change step</button> </> ), [state.step] );};
React.momo
和 React.useMemo
React.momo
在避免子组件从新渲染方面,是最简略的,在类组件外面有个React.PureComponent
,其作用也是。然而它无奈检测函数外部的状态变动,并且避免从新渲染,例如useContext
注入的状态。不过它主动比拟全副属性,应用起来方面。React.memo
依照依赖列表是否有属性扭转,决定是否返回新的值,肯定水平上和Vue
的计算属性相似,然而须要说动申明依赖的属性。相比React.momo
,它的管制的粒度更细,然而个别的内部属性变动,用这个显著没有React.memo
不便
useReducer
useContext
useReducer
是useState
的一种代替计划,useState
的外部实现就是useReducer
- 它接管两个参数,和
redux
一样,一个是reducer
, 一个是初始值,有两个返回,始终是以后的state
,一个是dispatch
- 通过
dispatch
调用action
就能够批改state
外面的数据 - 实质的作用是,让数据和函数组件解耦,让函数组件只有收回
Action
,就能够批改数据,因为数据不在组件外部,也不必解决外部state
变动带来的effect
。 useContext
和useReducer
联合,肯定水平上能够实现一个React Redux
其余 Hook
useImperativeHandle
,搭配useRef
和forwardRefs
能够实现定制父组件能够援用子组件的属性和办法,而不是间接援用整个子组件的实例,在父组件须要调用子组件属性和办法,然而又不想全副属性和办法都给父组件调用的时候应用useLayoutEffect
应用的不多,作用和useEffect
一样,然而这个hook
是在组件变动后,DOM
节点生成后,渲染之前调用,区别于useEffect
是渲染之后调用,不太举荐应用,会阻塞渲染useDebugValue
可用于在React
开发者工具中显示自定义hook
的标签。相似Vue
组件用的name
或者React
组件中的displayName
,不影响代码运行
组件复用
React Hook
有自定义 Hook
,React
类组件有高阶组件或者渲染属性
有个比拟常见的场景,进入页面须要调用后端接口的问题,如果每个组件都写一次,很繁琐,假如解决数据的接口长这样子
interface Response<T> { /** 是否有谬误 */ hasError: boolean; /** 是否有数据 */ hasData: boolean; /** 是否申请中 */ Loading: boolean; /** 申请回来的数据 */ data: T;}
高阶组件
高阶组件(HOC)
是 React
中用于复用组件逻辑的一种高级技巧。HOC
本身不是 React API
的一部分,它是一种基于 React
的组合个性而造成的设计模式。这种组件复用还挺常见的,比方 React-redux
外面的 connect
,React Router
的 withRouter
它能够做到:
- 属性代理,比方多个组件都应用到的公共属性,注入属性
- 包裹组件,比方将组件包裹在写好的容器外面
- 渲染挟持,比方权限管制
用途
- 代码复用
- 性能监测 打点
- 权限管制,依照不懂的权限等级,渲染不同的页面
高阶组件编写和应用
按下面申请的需要,做一个组件渲染完之后,就立刻开始申请初始数据
function requestWrapper(options) { return (Component) => { return class WapperComponent extends React.Component { constructor(props) { super(props); this.state = { loading: false, hasError: false, hasData: false, data: null, }; } httpRequest = async () => { this.setState({ loading: true }); // 这里做一些申请工作,我这里就不写了,没必要 this.setState({ hasData: true }); this.setState({ hasError: false }); this.setState({ data: { /** 这里是申请回来的数据 */ }, }); this.setState({ loading: false }); }; componentDidMount() { this.httpRequest(); } render() { // 透传内部传过来的属性,而后合并 this.state,传给包裹组件 const combineProps = { ...this.props, ...this.state }; return this.state.loading ? ( <Loading /> ) : ( <Component {...combineProps} /> ); } }; };}
应用方面,高阶组件能够润饰类组件或者函数组件
function Page(props) {// props 蕴含 loading hasError hasData data prop1// 其中 prop1 来源于内部属性,其余的属性来源于包裹的组件}// 类组件应用,应用装璜器@requestWrapper({url: '', param: {} })class ClassComponent extends React.PureComponent {}// 类组件应用,不实用装璜器export default requestWrapper({url: '', param: {} })(ClassComponent);// 函数式组件应用const WrapPage = requestWrapper({ url: '', param: {} })(Page);export default WrapPage;// 应用<WrapPage prop1="1" />;
自定义 Hook
的编写和应用
自定义 Hook
的编写,一个简略的数据申请的 Hook
function useRequest(options) { const [loading, setLoading] = useState(false); const [hasError, setHasError] = useState(false); const [hasData, setHasData] = useState(false); const [data, setData] = useState(null); useEffect(() => { async function reqeust() { setLoading(true); /** 这里仍旧是申请,没写 */ setHasError(false); setHasData(true); setData({ /** 申请回来的数据 */ }); setLoading(false); } reqeust(); }, []); return { loading, hasError, hasData, data };}
自定义 hook
只能在函数式组件应用,不能在类组件外面用
function Page(props) { // 这次的 props 只有 prop1 这个属性 const { loading, hasError, hasData, data } = useRequest({ url: ‘’, param: {}, });}<Page prop1={1} />;
函数式式组件和类组件默认属性
class Button extends React.PureComponent { static defaultProps = { type: "primary", onChange: () => {} };}// 不论是函数式还是类都能够这么玩,这也是类动态属性的另外一种写法Button.defaultProps = { type: "primary", onChange: () => {}};function Button({ type, onChange }) {}// 这样写看起来没问题,然而实际上,如果父组件没传 onChange,onChange// 每次组件渲染都会生成一个新的函数,援用类型都有这个问题function Button({ type = "primary", onChange = () => {} }) {}// 这很OK,你可真是个小机灵鬼const changeMet = () => {}function Button({ type = “primary”, onChange = changeMet }) {}
类组件的问题被解决了么?
复用组件状态逻辑难
- 依赖自定义的 Hook,能够解决组件状态和逻辑复用的问题,然而自定义
Hook
编写须要对Hook
运行机制十分理解,门槛并不比高阶组件低
生命周期带来的负面影响,逻辑拆分重大
- 生命周期拆分逻辑的问题,在
Hook
外面切实被解决了,不会存在同一个逻辑被拆分在N
个生命周期外面了
This
的指向问题
- 这个问题在 Hook 外面也是解决了,因为函数没有
this
,就不会有this
的问题,然而绝对的,如果须要一个不变的对象,请应用useRef
简略总结
useState
能够实现相似state
和setState
的成果useEffect
能够实现componentDidMount
componentDidUpdate
componentWillUnmount
这几个生命周期的性能,并且写法更加简略,在每次渲染后都会触发,触发的条件是依赖项有扭转useRef
返回一个援用,每次渲染都返回同一个对象,和类组件this
属性统一useCallback
返回一个记忆化的回调函数,在依赖项扭转的时候,回调函数会批改,否则返回之前的回调函数,对于一些须要传给子组件的函数,能够应用这个,防止子组件因为回调函数扭转而扭转useMemo
返回一个记忆化的值,依赖项扭转,返回的值才会变,可用来记忆化值,和Vue
计算属性相似,防止反复计算,防止反复渲染- 自定义的
Hook
是实现状态和逻辑复用,作用和高阶组件还有渲染属性差不多 useReducer
是useState
的底层实现,能够治理多个state
,把state
从组件外部抽离进去useContext
能够实现批量传值,注入多个组件,和useReducer
useMemo
应用能够实现Redux
的性能
应用感触
集体应用方面
- 函数式组件自身写起来就比类组件少写不少代码
- 闭包问题很影响开发和调试,进步了不少调试老本,如果不相熟闭包机制,很难发现问题。
Hook
中的闭包问题,大多还是因为依赖项没有填写导致 - 闭包带来的问题,比类组件
This
的更加宜人,次要调试不好发现问题,填不填依赖项也是一个让人纠结的活 Hook
的依赖不能自动识别,必须手动申明,尽管有插件辅助增加,然而应用起来还是不如Vue
的Hook
- 在相熟
Hook
的机制的状况下,Hook
开发体验还是比类组件好很多
团队合作方面
- 其实在推广
Hook
的时候,团队成员的Hook
程度是不太统一的,很多人员就遇到了闭包问题,还有依赖死循环的问题,这个可能大大小小都遇到过,就如同下面提到的,解决闭包问题,形式形形色色,其实也是我本人摸索过去的,而后看到团队成员其实差不多还使用者state
更新之后,从新设置监听的形式,这个并不是太好,只能说闭包问题解决了 - 绝对的,
React
官网也没有总结太多最佳实际,很多都靠本人实际过去的,所以团队成员在刚接触Hook
的时候,都是useEffect
useState
两把API
,甚至在React Hook
的官网文档外面 Hook 简介,对于这两个Hook
介绍的很多 - 但对于其余罕用的
Hook
,比方useRef
和useCallback
应用场景其实没有太好的例子去撑持这些API
的应用。倒是其实团队外面不少成员,面对着不参加渲染的属性,也是用useState
,而不是应用useRef
。就是很多新人接触Hook
容易犯的一个谬误。 - 有不少同学有些插件没有装上,导致
React
自动检测依赖项的插件没有失效,这无疑会给自身就难以发现的闭包问题加了一层霜 - 所以我也定期在团队外面分享我认为是比拟好的实际,去疏导团队外面的同学
- 对于不喜爱用
React Hook
的同学,间接用类组件,类组件尽管代码写起来繁琐,然而起码没有闭包这些问题,而且代码被接手之后容易读懂,React Hook
只是一个工具,会应用会给你加分,然而不会应用只会用类组件,也不会对其他人代码有影响,比拟类组件和函数组件是能够共存的