useState原理
function App() {
const [n, setN] = React.useState(0);
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
);
}
答:执行setN的时候会渲染UI,n不会变(setN不会改变n),App()会重新执行
分析
setN
-
setN一定会修改数据x,将n+1存入x
-
setN一定会触发 重新渲染(re-render)
useState
- useState 肯定会从x 读取n 的最新值
x
- 每个组件有组件的数据x,我们将其命名为state
实现React.useState
没有变化,因为myUseState 会将state重置,我们需要一个不会被myUseState重置的变量,那么这个变量只要声明在myUseState外面即可
如果一个组件用了两个useState怎么办?
把_state做成一个对象
-
比如
_state={n:0,m:0} -
不行,因为
useState(0)并不知道变量叫n还是m
把_state做成数组
- 比如
_state=[0,0]
_ state数组方案缺点
- useState调用顺序
若第一次渲染时n是第一个,m是第二个,k是第三个;则第二次渲染时必须保证顺序完全一致,所以React不允许出现下面的代码
总结:
-
每个函数组件对应一个React节点
-
每个节点保存着state和index
-
useState会读取state[index]
-
index由useState出现的顺序决定
-
setState会修改state,并触发更新
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--无bug
-
二、点击log再点击+1--有bug
-
是因为
贯穿始终的状态
全局变量
- 用window.xxx即可,但是太low了
useRef
- useRef不仅可以用于div,还能用于任意数据
function APP() {
const nRef = React.useRef(0)//{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>
)
}
- 上面的代码点击的时候不会改变,但是打印出的值改变了,因为NRef.current+1时,不会进行页面上的渲染(不会让APP重新渲染)
修改后的代码:
function APP() {
const nRef = React.useRef(0)//{current:0}
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000)
const update = React.useState(null)[1]
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>
)
}
useContext
- useContext不仅能贯穿始终,还能贯穿不同组件
const themeContext = React.createContext(null)
function App() {
const [theme, setTheme] = React.useState("red");
return (
<themeContext.Provider value={{ theme, setTheme }}>
<div className={`App ${theme}`}>
<p>{theme}</p>
<div>
<ChildA />
</div>
<div>
<ChildB />
</div>
</div>
</themeContext.Provider>
);
}
function ChildA() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("red")}>red</button>
</div>
);
}
function ChildB() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("blue")}>blue</button>
</div>
);
}
总结
-
每次重新渲染,组件函数就会执行
-
对应的所有state都会出现【分身】
-
如果不希望分身出现,可以用useRef/useContext等