共计 5514 个字符,预计需要花费 14 分钟才能阅读完成。
Hook 规定
- 只在最顶层应用
Hook
,不在条件、循环或者嵌套函数中应用Hook
- 只在 React 函数式组件或自定义 Hook 中应用
Hook
为什么 Hook 高度依赖执行程序?
Hook
存储在组件的公有属性中 __hooks_list
数组中。读取和存储都依赖 currentIndex
,如果hook
的执行程序扭转,currentIndex
获取的 hook
可能是实现谬误的。
Effect Hook
Effect Hook 能够在函数组件中执行一些具备 side effect(副作用)的操作
参数
- 回调函数: 在组件第一次
render
和之后的每次update
后运行,React
保障在DOM
曾经更新实现后才会回调。 - 状态依赖(数组): 当配置了状态依赖项后,只有检测倒配置状态变动后,才会调用回调函数。
useEffect(() => {// 只有组件 render 后执行}, []);
useEffect(() => {// 只有 count 扭转时才会执行}, [count]);
回调函数返回值
useEffect
的第一个参数能够返回一个函数,这个函数会在页面更新渲染后,执行下次 useEffect
之前调用。这个函数是对上一次调用 useEffect
进行清理。
export default function HookTest() {const [count, setCount] = useState(0);
useEffect(() => {console.log(` 执行... 以后 count: ${count}`);
return () => {console.log(` 革除... 以后 count: ${count}`);
}
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick-{() => setCount(count + 1)}>click me</button>
</div>
);
}
执行下面的代码,并点击几次按钮,会失去上面的后果:
执行... 以后 count: 1
清理... 以后 count: 1
执行... 以后 count: 2
清理... 以后 count: 2
执行... 以后 count: 3
清理... 以后 count: 4
如果加上浏览渲染的状况,后果应该是这样的:
页面渲染...1
执行... 以后 count: 1
页面渲染...2
清理... 以后 count: 1
执行... 以后 count: 2
页面渲染...3
清理... 以后 count: 2
执行... 以后 count: 3
页面渲染...4
清理... 以后 count: 3
执行... 以后 count: 4
那为什么浏览器在渲染完后,再执行清理的办法还能找到上一次的 state
呢?起因很简略,咱们再 useEffect
中返回的是一个函数,造成了一个闭包,这能保障咱们上一次执行函数存储的变量不会被销毁和净化。
举例会更好了解:
let flag = 1;
let clean;
function effect(flag) {console.log(`effect...curFlag: ${flag}`);
return function() {console.log(`clean...curFlag: ${flag}`);
}
}
clean = effect(flag);
flag = 2;
clean();
clean = effect(flag);
flag = 3;
clean();
clean = effect(flag);
clean();
// effect...curFlag: 1
// clean...curFlag: 1
// effect...curFlag: 2
// clean...curFlag: 2
// effect...curFlag: 3
// clean...curFlag: 3
模仿 componentDidMount
componentDidMount
等价于 useEffect
的回调,仅在页面初始化实现后执行一次。当 useEffect
的第二个参数传入一个空数组时就能够实现这种成果。
function uesDidMount(callback) {useEffect(callback, []);
}
官网不举荐这种写法,因为可能会导致一些谬误。
模仿 componentWillUnMount
function useUnMount(callback) {useEffect(() => callback, []);
}
不像
componentDidMount
或者componentDidUpdate
,useEffect
中应用的 effect 并不会阻止浏览器渲染页面。这让页面渲染看起来更加流程。
LayoutEffect Hook
红圈中是同步操作
useLayoutEffect
和 useEffect
相似,但不同的是:
useEffect
不会阻塞浏览器的重绘useLayoutEffect
会阻塞浏览器的重绘。如果须要手动批改dom
,举荐应用useLayoutEffect
。因为如果在useEffect
中更新dom
,useEffect
不会阻塞浏览器重绘,用户可能会看到因为更新导致的闪动。
ref Hook
应用 useRef Hook
,你能够轻松获取dom
的ref
。
export default function Input() {const inputEl = useRef(null);
const onButtonClick = () => {inputEl.current.focus();
};
return (
<div>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus this input</button>
</div>
);
}
useRef
不仅仅能够用来当作获取 dom
的ref
。还能够通过 useRef
产生的 ref
的current
属性的可变性,用它来保留任意值。
模仿 componentDidUpdate
componentDidUpdate
就相当于第一次调用的 useEffect
,借助useRef
生成一个标识,来记录是否为第一次执行:
function useDidUpdate(callback, prop) {const init = useRef(true);
useEffect(() => {if (init.current) {init.current = false;} else {return callback();
}
}, prop)
}
useCallback 和 useMemo 的异同
在 React
中,性能优化点在于:
- 调用
setState
,就会触发组件的从新渲染,不管state
是否变动 - 父组件更新,子组件也会更新
基于以上两点,useCallback
和 useMemo
就是解决性能问题的杀手锏。
useCallback
和 useMemo
的异同:
共同点:
仅仅是 依赖数据发生变化 时,才会从新计算结果,起到缓存作用。
两者区别:
useMemo
计算结果是return
回来的值,次要用于缓存计算结果的值。利用场景: 须要计算的状态。useCallback
计算结果是函数
,次要用于缓存函数。
useCallback
useCallback
返回的是缓存的函数,最简略的用法:
const fnA = useCallback(fnB, [a]);
当依赖 a
变更时,会返回新的函数。咱们无奈很好的判断返回函数是否变更,能够借助 ES6
新增的数据类型 Set
来判断,具体如下:
const set = new Set();
export default function Callback() {const [count, setCount] = useState(0);
const [val, setVal] = useState('');
const callback = useCallback(() => {console.log(count);
}, [count]);
set.add(callback);
return(
<div>
<h4>{count}</h4>
<h4>{set.size}</h4>
<div>
<button onClick={() => setCount(count + 1)}>add</button>
<input value={val} onChange={e => setVal(e.target.value)} />
</div>
</div>
);
}
每次批改 count
,set.size
都会 +1,这阐明 useCallback
依赖变量 count
变动时,会返回新的函数。而 val
变动时,set.size
无变动,阐明返回的是缓存的函数。
晓得 useCallback
特点后,有什么作用呢?
应用场景:有一个父组件,蕴含子组件,子组件接管一个函数作为 peops
。通常而言,如果父组件更新了,子组件也会执行。但大多数状况下,更新是没有必要的。咱们能够借助useCallback
来返回函数,而后把这个函数作为 props
传递给子组件;这样,子组件就能防止不必要的更新。
function Parent() {const [count, setCount] = useState(0);
const [val, setVal] = useState('');
const callback = useCallback(() => {return count;}, [count]);
return (
<div>
<h4>{count}</h4>
<Child callback={callback} />
<div>
<button onClick={() => setCount(count + 1)}></button>
<input value={val} onChange={e => setVal(e.target.val)} />
</div>
</div>
);
}
function Child({callback}) {const [count, setCount] = useState(() => callback());
return (<div>{count}</div>
);
}
useMemo
咱们先来看个 反例:
export default function withoutMemo() {const [count, setCount] = useState(1);
const [val, setVal] = useState('');
function expensive() {console.log('compute');
let sum = 0;
for(let i = 0; i < count * 1000; i ++) {sum += i;}
return sum;
}
return (
<div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}></button>
<input value={val} onChange={e => setVal(e.target.val)} />
</div>
</div>
);
}
这里创立了两个 state
,而后通过expensive
函数,执行一次低廉的计算,拿到 count
对应的某个值。咱们能够看到:无论是批改 count
还是 val
,因为组件的从新渲染,都会触发expensive
的执行。然而这里的低廉计算只依赖于 count
的值,在 val
批改的时候,是没有必要再次计算的。
在这种状况下,咱们就能够应用 useMemo
,只在count
的值批改时,执行 expensive
计算:
export default function withMemo() {const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const expensive = useMemo(() => {console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {sum += i;}
return sum;
}, [count]);
return (
<div>
<h4>{count}-{val}-{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}></button>
<input value={val} onChange={e => setVal(e.target.val)} />
</div>
</div>
);
}
下面咱们能够看到,应用 useMemo
来执行低廉的计算,而后将计算值返回,并且将 count
作为依赖值传递进去。这样,就只会在 count 扭转的时候触发 expensive
执行,在批改 val
的时候,返回上一次缓存的值。
同时也能够用来长久化一个执行函数,防止子组件的反复渲染,例如:
function Parent() {const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useCallback(() => {return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a + b);
}, [count]);
return (
<div>
<Child getNum={getNum} />
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={e => setValue(e.target.value)}/>
</div>
</div>
);
}
const Child = React.memo(function ({ getNum}) {return (<h4> 总和:{getNum()}</h4>);
});