useState

const [state, setState] = useState(initialState);

返回一个 state,以及更新 state 的函数。

在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。

setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

setState(newState);

在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。

函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState该函数将接收先前的 state,并返回一个更新后的值。下面的计数器组件示例展示了 setState 的两种用法:

function Counter() {const [count, setCount] = useState(0);function handleClick() {setCount(count + 1)}function handleClickFn() {setCount((prevCount) => {return prevCount + 1})}return (<>Count: {count}<button onClick={handleClick}>+</button><button onClick={handleClickFn}>+</button></>);
}

两种方式的区别

注意上面的代码,handleClickhandleClickFn一个是通过一个新的 state 值更新,一个是通过函数式更新返回新的 state。现在这两种写法没有任何区别,但是如果是异步更新的话,那你就要注意了,他们是有区别的,来看下面例子:

function Counter() {const [count, setCount] = useState(0);function handleClick() {setTimeout(() => {setCount(count + 1)}, 3000);}function handleClickFn() {setTimeout(() => {setCount((prevCount) => {return prevCount + 1})}, 3000);}return (<>Count: {count}<button onClick={handleClick}>+</button><button onClick={handleClickFn}>+</button></>);
}

当我设置为异步更新,点击按钮延迟到3s之后去调用setCount函数,当我快速点击按钮时,也就是说在3s多次去触发更新,但是只有一次生效,因为 count 的值是没有变化的。

当使用函数式更新 state 的时候,这种问题就没有了,因为它可以获取之前的 state 值,也就是代码中的 prevCount 每次都是最新的值。

其实这个特点和类组件中 setState 类似,可以接收一个新的 state 值更新,也可以函数式更新。如果新的 state 需要通过使用先前的 state 计算得出,那么就要使用函数式更新。

因为setState更新可能是异步,当你在事件绑定中操作 state 的时候,setState更新就是异步的。

class Counter extends React.Component {constructor(props) {super(props)this.state = { count: 0 }}handleClick = () => {this.setState({ count: this.state.count + 1 })this.setState({ count: this.state.count + 1 })// 这样写只会加1}handleClickFn = () => {this.setState((prevState) => {return { count: prevState.count + 1 }})this.setState((prevState) => {return { count: prevState.count + 1 }})}render() {return (<>Count: {this.state.count}<button onClick={this.handleClick}>+</button><button onClick={this.handleClickFn}>+</button></>);}
}

当你在定时器中操作 state 的时候,而 setState 更新就是同步的。

class Counter extends React.Component {constructor(props) {super(props)this.state = { count: 0 }}handleClick = () => {setTimeout(() => {this.setState({ count: this.state.count + 1 })this.setState({ count: this.state.count + 1 })// 这样写是正常的,两次setState最后是加2}, 3000);}handleClickFn = () => {this.setState((prevState) => {return { count: prevState.count + 1 }})this.setState((prevState) => {return { count: prevState.count + 1 }})}render() {return (<>Count: {this.state.count}<button onClick={this.handleClick}>+</button><button onClick={this.handleClickFn}>+</button></>);}
}

注意这里的同步和异步指的是 setState 函数。因为涉及到 state 的状态合并,react 认为当你在事件绑定中操作 state 是非常频繁的,所以为了节约性能 react 会把多次 setState 进行合并为一次,最后在一次性的更新 state,而定时器里面操作 state 是不会把多次合并为一次更新的。

注意:与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。

性能优化

React 使用 Object.is 比较算法来比较 state。

在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。

function Child({ onButtonClick, data }) {console.log('Child Render')return (<button onClick={onButtonClick}>{data.number}</button>)
}function App() {const [number, setNumber] = useState(0)const [name, setName] = useState('hello') // 表单的值const addClick = () => setNumber(number + 1)const data = { number }return (<div><input type="text" value={name} onChange={e => setName(e.target.value)} /><Child onButtonClick={addClick} data={data} /></div>)
}

如要避免不必要的子组件的重渲染,使用 React.memo 仅检查 props 变更。默认情况下其只会对复杂对象做浅层对比。所有使用 memo 优化后的代码如下:

function Child({ onButtonClick, data }) {console.log('Child Render')return (<button onClick={onButtonClick}>{data.number}</button>)
}
Child = memo(Child); // 在这里优化了
function App() {const [number, setNumber] = useState(0)const [name, setName] = useState('hello') // 表单的值const addClick = () => setNumber(number + 1)const data = { number }return (<div><input type="text" value={name} onChange={e => setName(e.target.value)} /><Child onButtonClick={addClick} data={data} /></div>)
}

你以为代码中的Child = memo(Child);已经优化了吗,然而并没有,当你在更改了父组件的状态,子组件依然会重新渲染,因为这关系到了React是如何浅层比较的,在子组件中onButtonClick 和 data 都是引用类型,所以他们是始终都不相等的,也就是[]===[]这样比较时始终返回false,在基本数据类型比较时memo才会起作用。

关于如何解决这个问题,我们就要使用两个新的API,useMemo和useCallback的Hook。下面是经过优化之后的代码。

function Child({ onButtonClick, data }) {console.log('Child Render')return (<button onClick={onButtonClick}>{data.number}</button>)
}Child = memo(Child)function App() {const [number, setNumber] = useState(0)const [name, setName] = useState('hello') // 表单的值const addClick = useCallback(() => setNumber(number + 1), [number])const data = useMemo(() => ({ number }), [number])return (<div><input type="text" value={name} onChange={e => setName(e.target.value)} /><Child onButtonClick={addClick} data={data} /></div>)
}export default App;

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

useCallback返回一个 memoized 回调函数。useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。主要区别是 React.useMemo 将调用 fn 函数并返回其结果,而 React.useCallback 将返回 fn 函数而不调用它。

useState用法指南相关推荐

  1. 【转】SASS用法指南

    SASS用法指南 阮一峰的,偏sass用法教程 sass入门 偏实战的基础用法

  2. curl 的用法指南

    curl 的用法指南 转自:http://www.ruanyifeng.com/blog/2019/09/curl-reference.html 作者: 阮一峰 日期: 2019年9月 5日 简介 c ...

  3. CSS: SASS用法指南 (附视频)

    观看视频:  CSS: SASS用法指南 学过CSS的人都知道,它不是一种编程语言. 你可以用它开发网页样式,但是没法用它编程.也就是说,CSS基本上是设计师的工具,不是程序员的工具.在程序员眼里,C ...

  4. SASS用法指南(转)

    学过CSS的人都知道,它不是一种编程语言. 你可以用它开发网页样式,但是没法用它编程.也就是说,CSS基本上是设计师的工具,不是程序员的工具.在程序员眼里,CSS是一件很麻烦的东西.它没有变量,也没有 ...

  5. 和could的区别用法_高考英语语法情态动词用法指南

    高考英语语法情态动词用法指南 情态动词有四类: ①只做情态动词:must,can(could),may(might)-- ②可做情态动词又可做实义动词:need,dare,will ③具有情态动词特征 ...

  6. ftp命令行登陆 用法指南

    转载:ftp命令行登陆 用法指南 1. ftp open *.*.*.* 这时会提示输入用户名和密码,输入完即可登陆. 可以用 dir或者ls命令查看当前目录内容 可以用 ascii或者binary改 ...

  7. VIM编辑器初学者用法指南——vim中无法使用冒号更改Ubuntu的输入法解决

    VIM编辑器初学者用法指南 一.vim打开文件: 二.vim编辑文件: 三.退出编辑模式 四.保存文件并退出Vim编辑器 Vim编辑器是Unix系统最初的编辑器,内置有两种操作模式:普通模式和插入模式 ...

  8. 【C++标准库】std::string用法指南源码剖析

    文章目录 1.ASCII码 (1)计算机如何表达字符 2.C 语言中的字符类型 char (1)思想:char 即整数 (3)C 语言帮手函数 (4)C语言中的字符串 (4)C 语言转义符 3.C++ ...

  9. Linux之curl命令用法指南学习

    Linux之curl命令用法指南学习 前言 常用参数 命令样例 其他 参考链接 前言 应用场景 工作中经常需要用到curl命令在测试或生产服务器中测试第三方接口,尤其是一些内外网接口.判断线上接口信息 ...

最新文章

  1. Ardino基础教程 8_模拟值
  2. 【工具使用系列】关于 MATLAB 液压机构,你需要知道的事
  3. RHEL6基础之八查找、文件内容查看类命令
  4. java字典树(Trie)实现中文模糊匹配
  5. CSDN 代码不能语法高亮的原因
  6. 后端开发(1)---大话后端开发的技巧大集合
  7. 微服务链路追踪_.NET Core微服务:分布式链路追踪系统分享
  8. 财经法规与会计职业道德3
  9. MXRuntimeUtils,替代 [NSObject performSelector object object ]的工具
  10. 【CCCC】L2-020 功夫传人 (25分),,模拟水题,多叉树的存储与遍历
  11. zigbee学习之定时器
  12. 老罗Android开发视频教程_基于JavaSE开发(适合Android初学者菜鸟级别的人)
  13. Jmeter的基本使用
  14. 广州三本找Java实习经历
  15. 18个Python高效编程技巧!
  16. 编写函数比较字符串的大小
  17. 网站被劫持的危害及处理方法
  18. 产品概念之1/4:前言 —— 有必要这么学术吗?
  19. 浅谈HTML5和H5区别
  20. elasticsearch 学习笔记3

热门文章

  1. 瞧瞧,这样的代码才叫 Pythonic
  2. Google 推出 Android 11 的 Developer Preview 3 版本
  3. 教你用 Android 做二次开发,识别率达到科大讯飞语音输入水平 | 原力计划
  4. 普通程序员想转人工智能,不知道它?别想了!
  5. 罗永浩出任“鲨纹科技”首席忽悠官;华为生产不含美国芯片的手机;PyCharm 2019.3 发布 | 极客头条...
  6. 收下这份说明书,原来迈进智能计算的大门如此简单
  7. 我如何不再是一个可怕的“10 倍经理”?
  8. 为什么 Facebook 的 Libra 会招来科技监管的雷霆之锤?
  9. 阿里 90 后科学家研发,达摩院开源新一代 AI 算法模型
  10. 重金悬赏丨2019 华为 IoT 开发者大赛喊你加入“群聊”,倾听科技的“声音”!...