state Hook——useState
作用
在函数式组件中定义state
import React, { useState } from 'React';
const [count, setCount] = useState(0);
useState做了啥
我们可以看到,在此函数中,我们通过useState定义了一个'state变量',它与 class 里面的 this.state 提供的功能完全相同.相当于以下代码。
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
useState参数
在代码中,我们传入了0作为useState的参数,这个参数的数值会被当成count初始值。当然此参数不限于传递数字以及字符串,可以传入一个对象当成初始的state。如果state需要储存多个变量的值,那么调用多次useState即可
useState返回值
返回值为:当前 state 以及更新 state 的函数,这与 class 里面 this.state.count 和 this.setState 类似,唯一区别就是你需要成对的获取它们。看到[count, setCount]很容易就能明白这是ES6的解构数组的写法。相当于以下代码
let _useState = useState(0);// 返回一个有两个元素的数组
let count = _useState[0];// 数组里的第一个值
let setCount = _useState[1];// 数组里的第二个值
读取状态值
只需要使用变量即可
以前写法
<p>You clicked {this.state.count} times</p>
现在的写法
<p>You clicked {count} times</p>
更新状态
通过setCount函数更新
以前写法
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
现在写法
<button onClick={() => setCount(count + 1)}>
Click me
</button>
这里setCount接收的参数是修改过的新状态值
声明多个state变量
我们可以在一个组件中多次使用state Hook来声明多个state变量
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的
为什么React要规定每次渲染它们时的调用顺序不变呢,这个是一个理解Hook至关重要的问题
注意:useState的初始值,只在第一次有效
举例:
当我点击按钮修改name的值的时候,我发现在Child组件,是收到了,但是并没有通过useState赋值给name!
const Child = memo(({data}) =>{
console.log('child render...', data)
const [name, setName] = useState(data)
return (
<div>
<div>child</div>
<div>{name} --- {data}</div>
</div>
);
})
const Hook =()=>{
console.log('Hook render...')
const [count, setCount] = useState(0)
const [name, setName] = useState('rose')
return(
<div>
<div>
{count}
</div>
<button onClick={()=>setCount(count+1)}>update count </button>
<button onClick={()=>setName('jack')}>update name </button>
<Child data={name}/>
</div>
)
}
运行结果:
children里的name始终展示的是rose,点击按钮设置为jack后,也还是显示rose
快照(闭包)vs 最新值
在开始前,先抛出这么一个问题。在 1s 内频繁点击10次按钮,下面代码的执行表现是什么
class App extends React.Component {
state = {
count: 0
}
increment = () => {
setTimeout(() => {
this.setState({
count: this.state.count + 1
});
}, 1000);
}
render() {
return <h1 onClick={ this.increment }>{this.state.count}</h1>;
}
}
如果是这段代码呢?它又会是什么表现?
function App() {
const [count, setCount] = useState(0);
const increment = () => {
setTimeout(() => {
setCount(count + 1);
}, 1000);
};
return <h1 onClick={increment}>{count}</h1>;
}
第一个例子,1s内连续点10次,从最开始点击1s后页面上的数字会从0增长到10。而第二个例子中,连续点击10次,页面上的数字只会从0增长到1。
这是为什么呢?其实这主要是引用和闭包的区别
class 组件里面可以通过 this.state 引用到 count,所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。
在 function component 里面每次更新都是重新执行当前函数,也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。
快照和引用的转换
如果我想让函数组件也是从0加到10,那么该怎么来解决呢?聪明的你一定会想到,如果模仿类组件里面的 this.state,我们用一个引用来保存 count 不就好了吗?没错,这样是可以解决,只是这个引用该怎么写呢?我在 state 里面设置一个对象好不好?就像下面这样:
const [state, setState] = useState({ count: 0 })
答案是不行,因为即使 state 是个对象,但每次更新的时候,要传一个新的引用进去,这样的引用依然是没有意义。
setState({
count: state.count + 1
})
useRef
想要解决这个问题,那就涉及到另一个新的Hook方法——useRef。useRef是一个对象,它拥有一个current属性,并且不管函数组件执行多少次,而useRef返回的对象永远都是原来那一个。
function App() {
const [count, setCount] = useState(0);
const ref = useRef(0);
const increment = () => {
setTimeout(() => {
setCount((ref.current += 1));
}, 2000);
};
return <h1 onClick={increment}>{count}</h1>;
}
useRef有下面几个特点:
- 1、useRef 是一个只能用于函数组件的方法。
- 2、useRef 是除字符串 ref、函数 ref、createRef 之外的第四种获取 ref 的方法。
- 3、useRef 在渲染周期内永远不会变,因此可以用来引用某些数据。
- 4、修改 ref.current 不会引发组件重新渲染。
useRef vs createRef
1、两者都是获取 ref 的方式,都有一个 current 属性。
2、useRef 只能用于函数组件,createRef 可以用在类组件中。
3、useRef 在每次重新渲染后都保持不变,而 createRef 每次都会发生变化。
关于 React.useRef() 返回的 ref 对象在组件的整个生命周期内保持不变,我们来和 React.createRef() 来做一个对比,代码如下:
function MyInput() {
const [count, setCount] = useState(0);
const myRef = createRef(null);
const inputRef = useRef(null);
console.log('执行');
// 仅执行一次,因为第二个参数是空数组
useEffect(() => {
console.log('sss');
inputRef.current.focus();
myRef.current.focus();
window.myRef = myRef;
window.inputRef = inputRef;
}, []);
// 每次componentDidUpdate都执行
useEffect(() => {
console.log('ddd');
console.log(myRef);
console.log(inputRef);
// 除了第一次为true, 其它每次都是 false 【createRef】
console.log('myRef === window.myRef', myRef === window.myRef);
// 始终为true 【useRef】
console.log('inputRef === window.inputRef', inputRef === window.inputRef);
});
return (
<>
<input type="text" ref={myRef} value="myRef" />
<input type="text" ref={inputRef} value="inputRef" />
<button onClick={() => setCount(count + 1)}>{count}</button>
</>
);
}
useState实现原理
import React from 'react';
import ReactDOM from 'react-dom';
let index = 0;
const lastStates = [];
function useState(initialState) {
lastStates[index] = lastStates[index] || initialState;
const current = index;
function setState(state) {
lastStates[current] = state;
render();
}
index += 1;
return [lastStates[current], setState];
}
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Mary');
console.log(count);
return (
<div>
<p>
click
{' '}
{count}
{' '}
times
</p>
<p>
name is
{' '}
{name}
</p>
<button type="button" onClick={() => { setCount(count + 1); }}>+</button>
<button type="button" onClick={() => { setName(`${Date.now()}`); }}>change Name</button>
</div>
);
}
function render() {
index = 0;
ReactDOM.render(
<App />,
document.getElementById('app')
);
}
render();
参考:
1、React Hooks原理与最佳实践:https://mp.weixin.qq.com/s/DS2OjlwWjClboDIgLFuG6A
state Hook——useState
作用
在函数式组件中定义state
useState做了啥
我们可以看到,在此函数中,我们通过useState定义了一个'state变量',它与 class 里面的 this.state 提供的功能完全相同.相当于以下代码。
useState参数
在代码中,我们传入了0作为useState的参数,这个参数的数值会被当成count初始值。当然此参数不限于传递数字以及字符串,可以传入一个对象当成初始的state。如果state需要储存多个变量的值,那么调用多次useState即可
useState返回值
返回值为:当前 state 以及更新 state 的函数,这与 class 里面 this.state.count 和 this.setState 类似,唯一区别就是你需要成对的获取它们。看到[count, setCount]很容易就能明白这是ES6的解构数组的写法。相当于以下代码
读取状态值
只需要使用变量即可
以前写法
现在的写法
更新状态
通过setCount函数更新
以前写法
现在写法
这里setCount接收的参数是修改过的新状态值
声明多个state变量
我们可以在一个组件中多次使用state Hook来声明多个state变量
React 假设当你多次调用 useState 的时候,你能保证每次渲染时它们的调用顺序是不变的
为什么React要规定每次渲染它们时的调用顺序不变呢,这个是一个理解Hook至关重要的问题
注意:useState的初始值,只在第一次有效
举例:
当我点击按钮修改name的值的时候,我发现在Child组件,是收到了,但是并没有通过useState赋值给name!
运行结果:
children里的name始终展示的是rose,点击按钮设置为jack后,也还是显示rose
快照(闭包)vs 最新值
在开始前,先抛出这么一个问题。在 1s 内频繁点击10次按钮,下面代码的执行表现是什么
如果是这段代码呢?它又会是什么表现?
第一个例子,1s内连续点10次,从最开始点击1s后页面上的数字会从0增长到10。而第二个例子中,连续点击10次,页面上的数字只会从0增长到1。
这是为什么呢?其实这主要是引用和闭包的区别
class 组件里面可以通过 this.state 引用到 count,所以每次 setTimeout 的时候都能通过引用拿到上一次的最新 count,所以点击多少次最后就加了多少。
在 function component 里面每次更新都是重新执行当前函数,也就是说 setTimeout 里面读取到的 count 是通过闭包获取的,而这个 count 实际上只是初始值,并不是上次执行完成后的最新值,所以最后只加了1次。
快照和引用的转换
如果我想让函数组件也是从0加到10,那么该怎么来解决呢?聪明的你一定会想到,如果模仿类组件里面的 this.state,我们用一个引用来保存 count 不就好了吗?没错,这样是可以解决,只是这个引用该怎么写呢?我在 state 里面设置一个对象好不好?就像下面这样:
答案是不行,因为即使 state 是个对象,但每次更新的时候,要传一个新的引用进去,这样的引用依然是没有意义。
useRef
想要解决这个问题,那就涉及到另一个新的Hook方法——useRef。useRef是一个对象,它拥有一个current属性,并且不管函数组件执行多少次,而useRef返回的对象永远都是原来那一个。
useRef有下面几个特点:
useRef vs createRef
1、两者都是获取 ref 的方式,都有一个 current 属性。
2、useRef 只能用于函数组件,createRef 可以用在类组件中。
3、useRef 在每次重新渲染后都保持不变,而 createRef 每次都会发生变化。
关于 React.useRef() 返回的 ref 对象在组件的整个生命周期内保持不变,我们来和 React.createRef() 来做一个对比,代码如下:
useState实现原理
参考:
1、React Hooks原理与最佳实践:https://mp.weixin.qq.com/s/DS2OjlwWjClboDIgLFuG6A