上半年学了一遍hook,今天因为某些原因又去翻了一遍文档,发现文档更详细了,也加了一些新的hook,赶紧补一下
useState
// 声明与初始化
const [ state, setState ] = useState(initialState)。
// 如果初始化的逻辑比较复杂,可以传入一个函数,在函数中计算并返回初始的state
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
})
// 更新值
setState(newState);
setState(prevState => prevState + 1);
setSatate(prevState => {
return { ...prevState, {name: 'sugarMei'}};
})
useState
指挥在组件初始化渲染中起作用,后续渲染会被忽略;- 在
setState
中,如果你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。
useEffect
常见的副作用有:改变 DOM、添加订阅、设置定时器、记录日志等,这些都可能产生莫名其妙的 bug 并破坏 UI 的一致性。
// 基本使用
useEffect(didUpdate);
// 清除effect, return的代码,会在执行下一个effect之前使用。
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
}
})
// effect会在每轮组件渲染完成后执行,性能并不好,所以可以使用第二个参数,告诉它执行的时机
// 当props.source发生改变时再执行
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
}
}, [props.source]);
// 如果想让useEffect只执行一次,就传入一个空数组
useEffect(() => {
// todo
}, [])
useEffect
的执行时机:在浏览器完成布局与绘制之后,传给useEffect
的函数会延迟调用;useEffect
将在组件更新前刷新上一轮渲染的effect
。
useContext
在Component
中,我们会使用static contextType = MyContext
或者 <MyContext.Consumer>
来获得MyContext.Provider
中提供的value
。
现在在函数组件中我们可以使用useContext
。
const name = 'sugarMei';
// 创建
const NameContext = React.creatContext(name);
function Parent(){
return (
<NameContext.Provider value="王花花">
<Son />
</NameContext.Provider>
)
}
function Son(){
return <GraSon />
}
function GraSon(){
const name = useContext(NameContext);
return <h1>{name}</h1>
}
useReducer
useState
的替代方案。假设我们的setState
时需要有一段逻辑来判断之后再赋值:
let value = '';
switch(props.atcion){
case 'name': value = 'sugarMei'; break;
case 'age': value = 20; break;
default: value = undefined; break;
}
state
逻辑较复杂且包含多个子值,或者下一个state
依赖于之前的 state
,useReducer
会比 useState
更适用,
const [ state, dispatch ] = useReducer(reducer, initialArg, init);
当调用dispatch
相当于 调用reducer
的逻辑,而init
这个函数,可以惰性地创建初始 state,也有利于在reducer
重置state
:
function init(initialCount){
return { count: initialCount };
}
function reducer(state, action){
switch(action.type){
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, initialCount, init);
useMemo
import React, { useState } from 'react';
function App(){
const [count, setCount] = useState(0);
const renderTitle = () => {
console.log('renderTitle');
return <h1>123</h1>;
}
return (
<>
<div>{renderTitle()}</div>
<p>{count}</p>
<button onClick={() => { setCount(count + 1)}}>+1</button>
</>
)
}
export default App;
每次修改count
都会引发renderTitle
被重新执行,可以使用useMemo
进行优化。
//const renderTitle = () => {
// console.log('renderTitle');
// return <h1>123</h1>;
// }
// 改写为
const renderTitle = useMemo(() => {
console.log('renderTitle');
return () => <h1>123</h1>;
}, [])
修改之后,修改count
并不会重新执行renderTitle
。
总结: useMemo
缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态
useCallback
<button onClick={addCount}>+1</button>
每次点击的时候回修改页面上的count
,引起页面渲染,而addCount
没必要重新渲染,所以:
const addCount = useCallback(() => {
setCount(count + 1)
}, [])
这样的话,addCount
只有在第一次的时候会渲染,然后就被缓存起来,后期组件重新渲染,也不会再重新创建这个函数。
总结: useCallback
缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数式没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费。
useRef
一个作用还是获得组件元素
import React, { useRef } from 'react';
function App(){
const inputEl = useRef(null);
const handleFoucs = ()=> {
inputEl.current.focus();
}
return (
<>
<input ref={inputEl} />
<button onClick={handleFoucs}>聚焦</button>
</>
)
}
export default App;
另一个作用是方便地保存任何可变值:
在以下这段代码中,我们在开始的时候弄了一个定时器,然后等到count
大于10的时候就清除定时器;但是每次count
改变,页面都会重新渲染,于是time
已经不是上一次保存那个time
了,所以页面没有在大于10 的时候解除定时器。
import React, { useState, useRef, useEffect } from 'react';
function App(){
const [ count, setCount ] = useState(0);
let time = null;
useEffect(() => {
time = setInterval(() => {
setCount(count => count + 1);
}, 1000)
}, [])
useEffect(() => {
if(count > 10){
clearInterval(time);
}
})
return (
<>
<button>count: {count}</button>
</>
)
}
export default App;
使用useRef
来初始化time
可以解决这个问题,通过.current
保存可变的值,放置才页面重新渲染的时候值被初始化。
import React, { useState, useRef, useEffect } from 'react';
function App(){
const [ count, setCount ] = useState(0);
const time = useRef();
useEffect(() => {
time.current = setInterval(() => {
setCount(count => count + 1);
}, 1000)
}, [])
useEffect(() => {
if(count > 3){
clearInterval(time.current);
}
})
return (
<>
<button>count: {count}</button>
</>
)
}
export default App;
总结:
useRef
可以获得DOM
元素;useRef.current
相当于一个全局变量,每次都会保持render
的最新状态,useState
的值在每个rernder
中都是独立存在的,类比于for循环中的let
和var
;useState
值得更像会触发组件重新渲染,而useRef.current
不会触发组件重新渲染。
useImperativeHandle
之前使用了useRef
获得input
标签,然后进行了聚焦。如果input
是包裹在一个子组件内部,那么需要借助forwardRef
:
import React, { forwardRef, useRef } from 'react';
const InputComponent = forwardRef((props, ref) => {
return (
<input ref={ref} />
)
})
function App(){
const inputEl = useRef(null);
const handleFoucs = ()=> {
inputEl.current.focus();
}
return (
<>
<InputComponent ref={inputEl} />
<button onClick={handleFoucs}>聚焦</button>
</>
)
}
export default App;
官方建议useImperativeHandle和forwardRef同时使用,减少暴露给父组件的属性,避免使用 ref 这样的命令式代码
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const InputComponent = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}))
return (
<input ref={inputRef} />
)
})
function App(){
const inputEl = useRef(null);
const handleFoucs = ()=> {
inputEl.current.focus();
}
return (
<>
<InputComponent ref={inputEl} />
<button onClick={handleFoucs}>聚焦</button>
</>
)
}
export default App;
useLayoutEffect
与
componentDidMount
或componentDidUpdate
不同,使用useEffect
调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffect
Hook 供你使用
useDebugValue
useDebugValue
可用于在 React 开发者工具中显示自定义 hook 的标签。
// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');