手写useState:
import ReactDOM from "react-dom";
import React from "react";
let _state = []
let index = 0
const myUseState = (initialState) => {
const currentIndex = index
_state[currentIndex] = _state[currentIndex] === undefined ? initialState : _state[currentIndex]
const setState = (newState) => {
_state[currentIndex] = newState
render()
}
index ++
return [_state[currentIndex], setState]
}
const render = () => {
index = 0
ReactDOM.render( <App/>,document.getElementById('root') );
}
const App = () => {
const [n, setN] = myUseState(0)
const [m, setM] = myUseState(0)
return <>
<div>
n:{n}
<button onClick={() => {setN(n + 1)}}>n+1</button>
</div>
<div>
m:{m}
<button onClick={() => {setM(m + 1)}}>m+1</button>
</div>
</>
}
export default App
总结:
- 每个函数组件对应一个React节点
- 每个节点保存着state和index
- useState会读取state[index]
- index由useState出现的顺序决定
- useSate有固定的出场顺序:
例如第一次渲染时,n是第一个,m是第二个,k是第三个;
那么第二次渲染时,必须保证完全一致的顺序;
所以useState不能放在条件判断里面去决定是否渲染。 - setState会修改state,并触发更新
- state每一次的改变都是一个新的state (但vue3没有产生新的state) =>useRef就是一个状态贯穿始终
以上解释是做了简化,React节点为FiberNode,_state
的名称为memorizedState,index的实现则用到了链。
一个小例来说明产生了新的n:
const App = () => {
const [n, setN] = React.useState(0)
const onClickN = () => {
setTimeout(() => {
console.log(n);
}, 3000)
}
return <div>
<div>n:{n}</div>
<button onClick={()=>setN(n+1)}>n+1</button>
<button onClick={onClickN}>点击,3秒后打印出n</button>
</div>
}
以上代码,两个按钮点击的先后顺序不用会打印出不同的结果哦
- 先点击'n+1'按钮,后点击'3秒后打印出n'按钮,3秒后打印出1,因为这时UI已经更新,打印出新的n
- 先点击'3秒后打印出n'按钮,后点击'n+1'按钮,3秒后打印出0,虽然这时UI已经更新,产生新的n:1,但指定打印的是旧的n,setN后得到的是新的n,新的n和旧的n是同时存在的。
setState注意事项
- state为对象时,不可局部更新,用以下方法解决:
function App(){
const [user, setUser] = React.useState({name:'fanlelee',age:18})
const onClick = ()=>{
setUser({ //给一个全新的对象
...user, //先复制一份原来的数据
name:'Lisa'
})
/*** 错误写法!!***/
user.name='Lisa'
setUser(user) //因为对象地址没有改变,React就认为数据没有变化
/*******/
}
return (
<div>
<button onClick={onClick}>Click</button>
</div>
)
}
因为setSate不会帮我们合并属性,另外useReducer也不会合并属性!!!
state如果是一个对象,
setState()
里面必须是一个新的对象,因为setState
如果对象地址不变,那么React就认为数据没有变化。setState接受函数(多次操作setState)
function App(){
const [n, setN] = React.useState(0)
const onClick = ()=>{
/*** 错误写法!!***/
setN(n+1)
setN(n+1) //n不会+2,只会+1
/*******/
//setN接受函数:
setN(x=>x+1)
setN(y=>y+1)
//以上,得到的结果是+2
}
return (
<div>
n:{n}
<button onClick={onClick}>Click</button>
</div>
)
}
useState注意事项
-
useState()
里面的初始值可以是一个函数,该函数返回一个对象;
因为有时候对象里面的值比较复杂,需要计算得到,把它写成一个“返回对象的函数”可以减少计算过程;
例:
useState({name:'Lisa',age:9+9}) //js引擎会去解析这个对象
useState(()=>({name:'Lisa',age:9+9})) //js引擎是不会立即执行函数的
- 当你发现用了多个
useState
的变量应该放在一个对象里面,这个时候可能用useReducer
更合适:
a. 所有在useState
里面的规则在useReducer
里面是一样的;
b. 总的来说useReducer
就是useState
的升级版;
c.useReducer
就是把参数的操作集中写在了reducer
里面;
d.useReducer
的好处就是用来践行Flux/Redux的思想。
const initialUser = {
name: 'fanlelee',
age: 18
};
function reducer(state, action) {
switch (action.type) {
case 'addAge':
return state = {...state, age: state.age + 1};
case 'changeName':
return state = {...state, ...action.data};
case 'reset':
return initialUser;
default :
throw new Error('unknown type')
}
}
function App() {
const [user, dispatch] = useReducer(reducer, initialUser);
return (
<div>
<div>
姓名:
<input type="text" value={user.name}
onChange={(e) => {
dispatch({
type: 'changeName',
data: {name: e.target.value}})
}}
/>
</div>
<div>
年龄:{user.age}
<button onClick={(e) => { dispatch({type: 'addAge'}) }}>
+1
</button>
</div>
<div>
<button onClick={(e) => { dispatch({type: 'reset'})}}>
重置
</button>
</div>
{JSON.stringify(user)}
</div>
);
}