赞
踩
在讲解之前我们先回忆下闭包,什么是闭包呢?
闭包是一个特殊的对象 它由两部分组成,执行上下文A以及在A中创建的函数B。
当B执行时,如果访问了A中的变量对象,那么闭包就会产生。
这里有一篇文档对闭包有详细描述和解释:https://juejin.cn/post/6844904014232944648
答:闭包是react hooks的核心。
const [n, setN] = useState(0)
useEffect(()=>{
console.log('useEffect')
},[])
const ref = useRef(null);
import React from "react"; import ReactDOM from "react-dom"; const rootElement = document.getElementById("root"); // 在当前模块中定义变量_state let _state; const myUseState = (initState) => { // 第一次调用时没有初始值,因此使用传入的初始值赋值 _state = ( _state === undefined ? initState : _state); const setState = (newState) => { _state = newState // 此方法能触发页面渲染 render() } return [_state,setState] } const render = () => ReactDOM.render(<App />, rootElement); function App() { // myUseState在App组件内执行,访问了_state变量,这里产生一个闭包 const [n, setN] = myUseState(0); // 根据闭包的特性,_state变量会持久存在,当App组件再次触发渲染时候,依然能够获取上一次_state值 return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p> </div> ); } ReactDOM.render(<App />, rootElement); 复制代码
import React from "react"; import ReactDOM from "react-dom"; const rootElement = document.getElementById("root"); let _state = []; let index = 0; const myUseState = (initState) => { let currentIndex = index; _state[currentIndex] = ( _state[currentIndex] === undefined ? initState : _state[currentIndex]); const setState = (newState) => { _state[currentIndex] = newState index = 0 render() } index += 1 return [_state[currentIndex],setState] } const render = () => ReactDOM.render(<App />, rootElement); function App() { const [n, setN] = myUseState(0); const [m, setM] = myUseState(0); return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => setN(n + 1)}>+1</button> </p> <p>{m}</p> <p> <button onClick={() => setM(m + 1)}>+1</button> </p> </div> ); } 复制代码
在上面的useMyState中,我们使用了数组来存放多个hook,然后react源码中使用了更高级的存储结构。
{
baseQueue: null, // 当前 update
baseState: 'hook1', // 初始值,即 useState 入参
memoizedState: null, // 当前状态(更新时表示上一次的状态)
queue: null, // 待执行的更新队列(queue.pending)
next: { // 下一个 hook
baseQueue: null,
baseState: null,
memoizedState: 'hook2',
next: null
queue: null
}
}
**hooks 以链表的形式存储在fiber节点的memoizedState属性上 **
源码分析:https://github.com/facebook/react/blob/main/packages/react/src/ReactCurrentDispatcher.js
举例说明,下面我们书写了几个hook
function App(){
const [count, setCount] = useState(1)
const [name, setName] = useState('chechengyi')
useEffect(()=>{
}, [])
const text = useMemo(()=>{
return 'ddd'
}, [])
}
分析:
1、在组件第一次渲染的时候,为每个hooks都创建了一个对象, 最终形成了一个链表.
2、在组件更新的过程中,hooks函数执行的顺序是不变的,就可以根据这个链表拿到当前hooks对应的Hook
对象,函数式组件就是这样拥有了state的能力。
注意:所以,知道为什么不能将hooks写到if else语句中了把?因为这样可能会导致顺序错乱,导致当前hooks拿到的不是自己对应的Hook对象。
在上面实现useState中,使用了闭包来存储state值,这就难免会引发一系列闭包问题,如内存溢出、闭包陷阱等。
举例什么是闭包陷阱?
function App(){
const [count, setCount] = useState(1);
useEffect(()=>{
setInterval(()=>{
console.log(count)
}, 1000)
}, [])
}
上面例子中中,我们持续触发setCount函数+1处理10次,等待1秒后打印结果还是1,那么为什么呢
每一次 render 都会生成一个闭包,每个闭包都有自己的 state 和 props(所以在异步函数中访问hooks的state值拿到的是当前的闭包值并不是最新的state值),所以打印出来的是1;
function App() { const [count, setCount] = useState(0); useEffect(() => { setTimeout(() => { alert("You clicked on: " + count); }, 3000); }, [count]) return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }
const FunctionComponent = () => { const [value, setValue] = useState(1) const countRef = useRef(value) useEffect(() => { countRef.current = value }, [value]) const log = useCallback( () => { setTimeout(() => { alert(countRef.current) }, 1000); }, [value], ) return ( <div> <p>FunctionComponent</p> <div>value: {value}</div> <button onClick={() => setValue(value + 1)}>add</button> <br/> <button onClick={log}>alert</button> </div> ) }
原理:useRef 每次 render 时都会返回同一个引用类型的对象,我们设置值和读取值都在这个对象上处理,这样就能获取到最新的 value 值了。
注意:createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。