为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳 GitHub 博客, 一年百来篇优质文章等着你!
到 React 16.8 目前为止,如果编写函数组件,然后遇到需要添加状态的情况,咱们就必须将组件转换为类组件。
编写 class Thing extends React.Component
,将函数体复制到 render()
方法中,修复缩进,最后添加需要的状态。
今天,可以使用 Hook 获得相同的功能,并为自己节省了工作时间。在本文中,主要介绍useState
hook。
useState 做啥子的
useState
hook 允许咱们向函数组件添加状态,我们通常称这些为“hooks”,但它们实际上是函数,与 React 16.8 捆绑在一起。通过在函数组件中调用useState
,就会创建一个单独的状态。
在类组件中,state
总是一个对象,可以在该对象上添加保存属性。
对于 hooks,state 不必是对象,它可以是你想要的任何类型 - 数组、数字、布尔值、字符串等等。每次调用 useState
都会创建一个 state
块,其中包含一个值。
示例:使用 useState 显示 / 隐藏组件
这个示例是一个组件,它显示一些文本,并在末尾显示一个 read more
链接,当单击链接时,它展开剩下的文本。
import React, {useState} from 'react';
import ReactDOM from 'react-dom';
// 两个 props:
// text - 显示的内容
// maxLength - 在点击“read more”之前显示多少个字符
function LessText({text, maxLength}) {
// 创建一个状态,并将其初始化为“true”const [hidden, setHidden] = useState(true);
if (text <= maxLength) {return <span>{text}</span>;
}
return (
<span>
{hidden ? `${text.substr(0, maxLength).trim()} ...` : text}
{hidden ? (<a onClick={() => setHidden(false)}> read more</a>
) : (<a onClick={() => setHidden(true)}> read less</a>
)}
</span>
);
}
ReactDOM.render(
<LessText
text={` 专注、努力是成功的真正关键。把你的眼睛盯在目标上,然后朝着目标迈出下一步 `}
maxLength={35}
/>,
document.querySelector('#root')
);
仅用一行代码,我们就使这个函数组件有状态:
const [hidden, setHidden] = useState(true);
但是这个函数到底在做什么呢? 如果每次渲染都调用它(确实如此),它又是如何保留状态的。
Hooks 实现的技巧
这里的“神奇”之处是,React 在每个组件的幕后维护一个对象,并且在这个持久对象中,有一个“状态单元”数组。当你调用 useState
时,React
将该状态存储在下一个可用的单元格中,并递增数组索引。
假设你的 hooks
总是以相同的顺序调用 (如果遵循 hooks 的规则,它们将是相同的顺序),React 能够查找特定useState
调用的前一个值。对 useState
的第一个调用存储在第一个数组元素中,第二个调用存储在第二个元素中,依此类推。
这也不是很神奇的事情,主要它依赖于你可能没有想过的事实:咱们写的的组件是由 React
调用,所以它可以在调用组件之前事先做好一些工作。而且,渲染组件的行为不仅仅是函数调用。像 <Thing />
这样的 JSX
被编译为React.createElement(Thing)
– 显然 React 可以控制它的调用方式和时间。
示例:根据之前的状态更新状态
看看另一个例子:根据前一个值更新 state
的值。
咱们要造个计步器,每点击一次按钮,就计一次,点击完后,它会告诉你你走了多少步。
import React, {useState} from 'react';
function StepTracker() {const [steps, setSteps] = useState(0);
function increment() {setSteps(steps => steps + 1);
}
return (
<div>
总共走了 {steps} 步!
<br />
<button onClick={increment}>
点点我,步数不是个事!</button>
</div>
);
}
ReactDOM.render(
<StepTracker />,
document.querySelector('#root')
);
首先,通过调用 useState
创建一个新的 state
,并将其初始化为0
。它返回steps
的当前值 0
和setSteps
函数来更新 steps
,用 increment
函数来对 steps
进行增 1 操作。
这里还可以优化的提取 increment
函数,可以直接将 increment
函数里面的内联到 onClick
里面:
<button onClick={() => setSteps(steps => steps + 1)}>
I took another step
</button>
示例:state 作为数组
记住,state
可以保存任何你想要的值。下面是一个随机数列表的例子,单击按钮将向列表添加一个新的随机数:
function RandomList() {const [items, setItems] = useState([]);
const addItem = () => {
setItems([
...items,
{
id: items.length,
value: Math.random() * 100}
]);
};
return (
<>
<button onClick={addItem}>Add a number</button>
<ul>
{items.map(item => (<li key={item.id}>{item.value}</li>
))}
</ul>
</>
);
}
注意,我们 state
初始化为空数组 []
,并在addItem
函数中更新值。
setItems
更新 state
不会将旧值“合并”– 它会使用新值覆盖 state
。这与this.setState
在类中的工作方式不同。
示例:具有多个键的 state
再来看看,state
为对象的例子,创建一个包含 2 个字段的登录表单:username
和password
。
下面示例主要展示如何在一个 state
对象中存储多个值,以及如何更新单个值。
function LoginForm() {const [form, setValues] = useState({
username: '',
password: ''
});
const printValues = e => {e.preventDefault();
console.log(form.username, form.password);
};
const updateField = e => {
setValues({
...form,
[e.target.name]: e.target.value
});
};
return (<form onSubmit={printValues}>
<label>
Username:
<input
value={form.username}
name="username"
onChange={updateField}
/>
</label>
<br />
<label>
Password:
<input
value={form.password}
name="password"
type="password"
onChange={updateField}
/>
</label>
<br />
<button>Submit</button>
</form>
);
}
如果想试试,可查看 CodeSandbox。
首先,我们创建一个 state
片段,并用一个对象初始化它
const [form, setValues] = useState({
username: '',
password: ''
})
这看起来像是在类中初始化状态的方式。
还有一个处理提交的函数,其中,e.preventDefault
来阻止页面刷新并打印出表单值。
updateField
函数更有意思。它使用 setValues
传递一个对象,为了确保现有的状态不被覆盖,这里使用了展开运算(...form
)。
代码部署后可能存在的 BUG 没法实时知道,事后为了解决这些 BUG,花了大量的时间进行 log 调试,这边顺便给大家推荐一个好用的 BUG 监控工具 Fundebug。
交流
干货系列文章汇总如下,觉得不错点个 Star,欢迎 加群 互相学习。
https://github.com/qq44924588…
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复 福利,即可看到福利,你懂的。