React添加交互
状态(state)与交互
在 React 中,随时间变化的数据被称为状态(state)。你可以向任何组件添加状态,并按需进行更新。
添加事件处理函数
事件处理函数是响应用户交互(如点击、输入等)的函数。
示例如下
- 在 Button 组件 内部 声明一个名为 handleClick 的函数。
- 实现函数内部的逻辑(使用 alert 来显示消息)。
- 添加
onClick={handleClick}到<button>JSX 中。
1
2
3
4
5
6
7
8
9
10
11
export default function Button() {
function handleClick() {
alert('你点击了我!');
}
return (
<button onClick={handleClick}>
点我
</button>
);
}
或者,你也可以在 JSX 中定义一个内联的事件处理函数:
1
2
3
<button onClick={function handleClick() {
alert('你点击了我!');
}}>
或者,直接使用更为简洁箭头函数:
1
2
3
<button onClick={() => {
alert('你点击了我!');
}}>
事件处理函数也可以做为props传递给子组件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Parent() {
function handleClick() {
alert('你点击了我!');
}
return <Child onClick={handleClick} />;
}
function Child(props) {
return (
<button onClick={props.onClick}>
点我
</button>
);
}
事件处理函数通常在组件内部定义,名称以handle开头,后跟事件名称.
传递函数而非调用(常见错误)
正确写法:
1
<button onClick={handleClick}>
错误写法:
1
<button onClick={handleClick()}>
在正确示例中,handleClick 函数作为 onClick 事件处理函数传递。这会让 React 记住它,并且只在用户点击按钮时调用你的函数。
在第二个示例中,handleClick() 中最后的 () 会在 渲染 过程中 立即 触发函数,即使没有任何点击。这是因为位于 JSX {} 之间的 JavaScript 会立即执行。
编写内联代码时,也会出现同样的问题
正确写法:
1
2
3
<button onClick={() => {
alert('你点击了我!');
}}>
错误写法:
1
<button onClick={alert('你点击了我!')}>
事件传播
事件传播是指当一个事件在 DOM 树中触发时,它会从事件源节点向上冒泡到其父节点,直到到达根节点.这意味着如果你在一个子元素上触发了一个事件,它也会触发其所有祖先元素上的相同事件.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Parent() {
function handleClick() {
alert('你点击了我!');
}
return <Child onClick={handleClick} />;
}
function Child(props) {
return (
<button onClick={props.onClick}>
点我
</button>
);
}
在上面的示例中,如果你点击按钮,它会触发Child组件上的onClick事件处理函数,然后事件会冒泡到Parent组件,触发其onClick事件处理函数.
在React中所有的事件都会传播,除了onScroll,它仅使用与你附加到的JSX标签
阻止事件传播
事件处理函数接收一个 事件对象 作为唯一的参数。按照惯例,它通常被称为 e ,代表 “event”(事件)。你可以使用此对象来读取有关事件的信息。
这个事件对象还允许你阻止传播。如果你想阻止一个事件到达父组件,你需要像下面 Button 组件那样调用 e.stopPropagation() :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
export default function Toolbar() {
return (
<div className="Toolbar" onClick={() => {
alert('你点击了 toolbar !');
}}>
<Button onClick={() => alert('正在播放!')}>
播放电影
</Button>
<Button onClick={() => alert('正在上传!')}>
上传图片
</Button>
</div>
);
}
当你点击按钮时:
React 调用了传递给 <button> 的 onClick 处理函数。 定义在 Button 中的处理函数执行了如下操作: 调用 e.stopPropagation(),阻止事件进一步冒泡。 调用 onClick 函数,它是从 Toolbar 组件传递过来的 prop。 在 Toolbar 组件中定义的函数,显示按钮对应的 alert。 由于传播被阻止,父级 <div> 的 onClick 处理函数不会执行。
阻止默认行为
在某些情况下,你可能想阻止浏览器的默认行为,例如提交表单时默认重新加载整个页面.你可以通过调用事件对象的preventDefault()方法来实现.
1
2
3
4
5
6
7
8
9
10
11
12
function Link() {
function handleClick(e) {
e.preventDefault();
alert('你点击了链接,但没有跳转!');
}
return (
<a href="https://www.example.com" onClick={handleClick}>
点我
</a>
);
}
State:组件的记忆
为什么需要state
- 局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
- 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
useState Hook 提供了这两个功能:
- State 变量 用于保存渲染间的数据。
- State setter 函数 更新变量并触发 React 再次渲染组件。
使用示例
1
const [index,setIndex] = useState(0);
index 是一个 state 变量,setIndex 是对应的 setter 函数。useState的唯一参数是变量的初始值
这里的 [ 和 ] 语法称为数组解构,它允许从数组中读取值。 useState 返回的数组总是正好有两项。
- 当一个组件需要在多次渲染间“记住”某些信息时使用 state 变量。
- State 变量是通过调用 useState Hook 来声明的。
- Hook 是以 use 开头的特殊函数。它们能让你 “hook” 到像 state 这样的 React 特性中。
- useState Hook 返回一对值:当前 state 和更新它的函数。
- Hooks 必须在组件的顶层调用,永远不要放在条件、循环或嵌套函数里。
- State 是组件私有的。如果你在两个地方渲染它,则每个副本都有独属于自己的 state。
State是一个快照
每次组件渲染时,state 变量都是该渲染的快照。即使在事件处理函数中访问 state 变量,它们也不会随着时间变化。
1
2
3
4
5
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
在上面的代码中,点击按钮时,number 的值只会增加 1,而不是 3。这是因为每次渲染时,number 都是该渲染的快照.
批量更新状态
React 会将多个状态更新合并为一次渲染,以提高性能。这意味着在同一个事件处理函数中多次调用状态更新函数,React 只会进行一次渲染。
如果想在同一次渲染中更新状态,可以使用函数式更新:
1
2
setNumber(prevNumber => prevNumber + 1);
setNumber(prevNumber => prevNumber + 1);
在这个例子中,react会调用两次传递的函数,每次传递上一次更新后的值,最终number会增加2.
命名惯例:通常可以通过相应 state 变量的第一个字母来命名更新函数的参数:
1
2
3
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
更新state中的对象
应当将state中的对象看做只读的。不要直接修改state对象,而是创建一个新对象,并将其传递给setter函数.
1
2
3
4
5
6
7
8
9
10
11
const [person, setPerson] = useState({
name: '张三',
age: 25
});
function haveBirthday() {
setPerson({
...person,
age: person.age + 1
});
}
只有通过setter函数更新state,react才会知道需要重新渲染组件.