hook 用在最顶层 不能包在if else 里面
function组件和class组件本质的区别
class Index extends React.Component<any,any>{
constructor(props){
super(props)
this.state={
number:0
}
}
handerClick=()=>{
for(let i = 0 ;i<5;i++){
setTimeout(()=>{
this.setState({ number:this.state.number+1 })
console.log(this.state.number)
},1000)
}
}
render(){
return <div>
<button onClick={ this.handerClick } >num++</button>
</div>
}
}
function Index(){
const [ num ,setNumber ] = React.useState(0)
const handerClick=()=>{
for(let i=0; i<5;i++ ){
setTimeout(() => {
setNumber(num+1)
console.log(num)
}, 1000)
}
}
return <button onClick={ handerClick } >{ num }</button>
}
上面得类组件和函数组件得打印结果?
------------公布答案-------------
在第一个例子🌰打印结果: 1 2 3 4 5
在第二个例子🌰打印结果: 0 0 0 0 0
这个问题实际很蒙人,我们来一起分析一下,第一个类组件中,由于执行上setState没有在react正常的函数执行上下文上执行,而是setTimeout中执行的,批量更新条件被破坏。原理这里我就不讲了,所以可以直接获取到变化后的state。
但是在无状态组件中,似乎没有生效。原因很简单,在class状态中,通过一个实例化的class,去维护组件中的各种状态;但是在function组件中,没有一个状态去保存这些信息,每一次函数上下文执行,所有变量,常量都重新声明,执行完毕,再被垃圾机制回收。所以如上,无论setTimeout执行多少次,都是在当前函数上下文执行,此时num = 0不会变,之后setNumber执行,函数组件重新执行之后,num才变化。
所以, 对于class组件,我们只需要实例化一次,实例中保存了组件的state等状态。对于每一次更新只需要调用render方法就可以。但是在function组件中,每一次更新都是一次新的函数执行,为了保存一些状态,执行一些副作用钩子,react-hooks应运而生,去帮助记录组件的状态,处理一些额外的副作用。
min-hooks模拟源码
let isMount = true; //是初始状态还是更新状态
let workInProgressHook = null // 指针当前正在运行的hook
const fiber = {
// 保存该对应的Hooks链表
memoizedState: null,
// 指向App函数
stateNode: APP
};
function useState (initialState) {
// 是哪个hook?当前useState对应的hook
let hookCurrent;
// 首次渲染(即便是首次渲染一个页面也可能使用多个useState,每一个useState都是一个hook)
if (isMount) {
hookCurrent = {
// 每一个hook 里面的更新可以调用多次updateNum
queue: {
pending: null
},
// memoizedStateHook 初始值//hooks当前更新的值
memoizedStateHook: initialState,
next: null,
// 指向App函数
stateNode: APP
}
if (!fiber.memoizedState) {
// 第一次执行时,当前执行片段的fiber的hook只有目前这一个hookValue
fiber.memoizedState = hookCurrent;
// 当前hook 指针指向这唯一的这个hookValue
workInProgressHook = hookCurrent;
} else {
// 由于第一也就是上面的if 执行过了所以fiber.memoizedState不再是空
// 之前的hook和现在当前的hook形成链表(由于目前workInProgressHook只想最后一个hook)
workInProgressHook.next = hookCurrent;
// 当前hook 指针指向这个hookValue
workInProgressHook = hookCurrent;
}
} else {
// 更新时候直接用初始化时候hook
hookCurrent = workInProgressHook;
workInProgressHook = workInProgressHook.next;
}
let baseState = hookCurrent.memoizedStateHook; // 上一次状态
// 存在要更新的值
if (hookCurrent.queue.pending) {
//第一个要更新的值
let firstUpdate = hookCurrent.queue.pending.next;
do {
const action = firstUpdate.action;
baseState = action(baseState); // 这是新的值
firstUpdate = firstUpdate.next;
} while (firstUpdate !== hookCurrent.queue.pending.next)
hookCurrent.queue.pending = null;
}
hookCurrent.memoizedStateHook = baseState;
return [baseState, dispatchAction.bind(null, hookCurrent.queue)];
}
function dispatchAction (queue, action) {
// 创建update ---每一次更新
const update = {
// action更新执行的函数(更改数据)
action,
// 与同一个Hook的其他更新形成链表
next: null
}
// 环状单向链表操作
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
// 模拟React开始调度更新
schedule();
}
// 首次render时是mount
isMount = true;
function schedule () {
console.log(fiber.memoizedState, '---')
// 更新前将workInProgressHook重置为fiber保存的第一个Hook
workInProgressHook = fiber.memoizedState;
// 触发组件render
const app = fiber.stateNode();
// 组件首次render为mount,以后再触发的更新为update
isMount = false;
return app
}
function APP () {
const [num, updateNum] = useState(0)
const [tel, updateTel] = useState(0)
console.log(`${isMount ? 'mount' : 'update'} num: `, num);
console.log(`${isMount ? 'mount' : 'update'} tel: `, tel);
return ({
onclick () {
updateNum(num => num + 1)
updateNum(num => num + 1)
updateNum(num => num + 1)
updateTel(tel => tel + 1)
updateTel(tel => tel + 1)
}
})
}
window.app = schedule();