前言:React 是一个用于构建用户界面的 JavaScript 库 。
Props
组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的 props。所有 React 组件都必须像纯函数(不更改自己的入参则是纯函数)一样保护它们的 props 不被更改。
State
- 不要直接修改State,应该使用setState()
- State 的更新可能是异步的:setState() 可以接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数
- State 的更新会被合并
生命周期
挂载卸载过程
- constructor()
完成React数据的初始化,接受两个参数:props、context。当想在函数内部使用这两个参数时,需使用super()传入这两个参数。只要使用了constructor(),就必须使用super(),否则会导致this指向错误。 - componentWillMount()
组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。一般用的比较少,它更多的是在服务端渲染时使用。 - componentDidMount()
组件第一次渲染完成,此时dom节点已经生成。在此处做数据处理。 - componentWillUnmount ()
在此处完成组件的卸载和数据的销毁。
更新过程
- componentWillReceiveProps (nextProps)
在接受父组件改变后的props需要重新渲染组件时 - shouldComponentUpdate(nextProps,nextState)
主要用于性能优化(部分更新) 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新 - componentWillUpdate (nextProps,nextState)
shouldComponentUpdate返回true以后,组件进入重新渲染的流程时 - componentDidUpdate(prevProps,prevState)
组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。 - render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。
React如何阻止无效重渲染
如果你的组件只有当 props中某个值或者 state中某个值改变才需要更新时,你可以使用 shouldComponentUpdate 来进行检查,返回true则渲染false则不渲染。
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
大部分情况下,可以继承React.PureComponent 实现。
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
当然这种方式也有不足的地方,它只是进行浅比较,当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。PureComponent 仅仅会对新老 this.props 的值进行简单的对比。避免该问题最简单的方式是避免更改你正用于 props 或 state 的值。
//原代码
handleClick() {
// 这部分代码很糟,而且还有 bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
//更正后
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
//ES6数组支持扩展运算符
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
//Object.assign
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
受控组件与非受控组件
event.preventDefault():阻止事件的默认行为,不会影响事件的传递
受控组件
在 HTML 中,表单元素(如
<input>
、<textarea>
、<select>
之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state属性中,并且只能通过使用 setState()来更新。
两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
console.log('文本框输入的值是:' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
非受控组件
使用非受控组件,表单数据交由DOM节点来处理。
- 创建Refs:可以通过==React.createRef()==创建Refs并通过ref属性联系到React组件。Refs通常当组件被创建时被分配给实例变量,这样它们就能在组件中被引用。
- 访问Refs: 当一个ref通过render放入一个元素中,一个对节点的引用可以通过ref的current属性得到;
const node = this.myRef.current;
- PS:在 React 中,
<input type="file" />
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.input= React.createRef();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log('文本框输入的值是:' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
如果只想得到表单的value,代码可以这样写,区别在于ref的定义
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log('文本框输入的值是: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={ref => this.input = ref} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
二者分别适用于什么场景
场景 | 受控组件 | 非受控组件 |
---|---|---|
一次性取值(例如在提交时) | ✔ | ✔ |
提交时验证 | ✔ | ✔ |
即时现场验证 | ✘ | ✔ |
有条件地禁用提交按钮 | ✘ | ✔ |
强制输入格式 | ✘ | ✔ |
一个数据的多个输入 | ✘ | ✔ |
动态输入 | ✘ | ✔ |
Hook
- Hook 是React 16.8的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
- Hook 在 class 内部是不起作用的。
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(自定义的 Hook 中可以调用Hook)
State Hook(useState)
- 调用 useState 方法的时: 它定义一个 “state 变量”。我们的变量叫 count, 但是我们可以叫他任何名字,比如 banana。这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
- useState 需要哪些参数:useState() 方法里面唯一的参数就是初始 state。不同于 class 的是,我们可以按照需要使用数字或字符串对其进行赋值,而不一定是对象。
- useState 方法的返回值是什么:当前 state 以及更新 state 的函数
import React, { useState } from 'react'; //引入useState
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p> //读取state,直接使用count
<button onClick={() => setCount(count + 1)}>Click me</button> //更新state,直接调用setCount
</div>
);
}
以上使用Hook的函数组件等同于如下的class组件
import React from 'react';
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Effect Hook(useEffect)
- Effect Hook 可以让你在函数组件中执行副作用操作,可以在这里告诉 React 组件需要在渲染后执行某些操作
- 可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合
- 默认情况下,useEffect 在第一次渲染之后和每次更新之后都会执行。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
- 允许按照代码的用途分离useEffect。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。
需要清除的 effect
- class组件的清除工作在生命周期函数componentWillUnmount 执行
- 而在这里,每个 effect 都可以返回一个清除函数,这是 effect 可选的清除机制
- effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。这个设计可以帮助我们创建 bug 更少的组件。(在 class 组件中,我们需要添加 componentDidUpdate来解决)
如何跳过 Effect 进行性能优化
- 在class组件中,通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决
- 而在这里,只要传递数组作为 useEffect 的第二个可选参数即可
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数
如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直拥有其初始值。
React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect