1、关于 useState
- 在学习 React 的 useState 时,肯定会遇到一个疑问,useState 到底做了什么,可以获得一个既可以读,又可以写入的数组呢?
- 执行 setN 的时候会发生什么?n 会变吗?App() 会重新执行吗?
- 如果 App() 会重新执行,那么 useState(0) 的时候,每次的值会有不同吗?
function App() {
const [n, setN] = useState(0);
const [m, setM] = useState(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>
);
}
分析:
- setN
- setN 一定会修改某个数据 x ,将 n+1 存入这个 x 中
- setN 一定会触发 重新渲染
- useState
- useState 肯定会从 x 读取 n 的最新值
- x
- 每个组件都有自己的数据 x,我们将其命名为 state
2、手写一个简易版 useState 的代替品 useMyState
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
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() {
const [n, setN] = myUseState(0);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
- 但是这样的代码也会遇到问题,如果除了 n 还有个 m 呢?
3、useMyState 方法进阶版
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>
);
}
- 使用数组方法的缺点:
- useState 的调用顺序非常重要
- 第一次渲染时 n 在第一个,m 第二个,k 第三个
- 那么第二次渲染时的调用顺序必须完全一致
- 因此,React 不允许出现 if...else..
- 示例:
const [n, setN] = React.useState(0);
let m, setM;
if (n % 2 === 1) {
[m, setM] = React.useState(0);
}
4、useState 的 n
function App() {
const [n, setN] = React.useState(0);
const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
//先点击 +1,再点 log,log 的值为 1
//先点击 log,再点 +1,log 的值为 0
- 因为每一次 useState 渲染出 App() 里的 n 都是一个新的 n,也就是说 n=0 和 n=1,是并存的,点击 log 后,log 出的是原来等于 0 的 n,而不是 +1 后生成的新的 n、
5、useRef
function App() {
const nRef = React.useRef(0);
//nRef 是一个对象 { current:0 }
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
return (
<div className="App">
<p>{nRef.current}</p>
<p>
<button onClick={() => (nRef.current += 1)}>+1</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
- 这样就可以解决上面 n 的分身的问题了,但是又会出现新的问题:UI 并不会进行实时的更新,也就是说 useRef 并不会触发渲染
- 解决方法:添加使用 useState 来触发更新(不推荐)
- 示例:
const nRef = React.useRef(0);
const update = React.useState()[1];
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
return (
<div className="App">
<p>{nRef.current} 这里并不能实时更新</p>
<p>
<button onClick={() => ((nRef.current += 1), update(nRef.current))}>
+1
</button>
<button onClick={log}>log</button>
</p>
</div>
);
6、总结
-
每个函数组件对应一个 React 节点
-
每个节点保存着 state 和 index
-
useState 会读取 state[index]
-
index 由 useState 出现的顺序决定
-
setState 会修改 state ,并触发更新
-
每次渲染,组件函数就会再次执行
-
对应的所有 state 都会出现一个“分身”
-
如果不希望出现这样“分身”的情况
-
可以使用 useRef / useContext 等