1. 组件
1.1 组件有函数式组件和Class类组件
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
1.2 渲染组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.createRoot(
document.getElementById('root')
).render(element);
// 自定义组件使用大写字母开头
import React from 'react';
// 正确!组件需要以大写字母开头:
function Hello(props) {
// 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的:
return <Hello toWhat="World" />;
}
1.3 受控组件 与 非受控组件
受控组件: 对某个组件状态的掌控,它的值是否只能由用户设置,而不能通过代码控制;
在HTML的表单元素中,他们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
// input自身维护的状态,外界无法获取数据
class TestComponent extends React.Component {
render () {
return <input name="username" />
}
}
// 可以设置初始值
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = { username: 'test' };
}
render () {
return <input name="username" value={this.state.username} />
}
}
// 可以读取并设置初始值
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
username: "test"
}
}
onChange (e) {
console.log(e.target.value);
this.setState({
username: e.target.value
})
}
render () {
return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
}
非受控组件: 组件内的状态不由用户控制;
// 如果不想关心表单元素的值是如何变化的,只想取值,可以使用ref
import React, { Component } from 'react';
export class UnControll extends Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) => {
console.log('我们可以获得input内的值为', this.inputRef.current.value);
e.preventDefault();
}
render () {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<input defaultValue="lindaidai" ref={this.inputRef} />
<input type="submit" value="提交" />
</form>
)
}
}
1.4 props
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
// 错误,要像纯函数一样幂等
function withdraw(account, amount) {
account.total -= amount;
}
1.5 state
// 使用props形式
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
// 如何避免多次React.DOM render?
// 引用生命周期,根组件保留一个
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
- setState
// 构造函数是唯一可以给state赋值的地方
this.setState({comment: 'Hello'});
- state更新可能是异步的
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
- state更新会合并
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
// 相当于{post: response.posts, ...otherState}
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
- 单向数据流 state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的; 例如Clock中计时器都是独立的
setState 异步
异步目的:batch 处理,性能优化
- 合成事件
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
- 生命周期
class App extends Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
- 原生事件
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
- setTimeout
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
- 批处理
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
- setState 只在合成事件和生命周期是“异步”的,在原生事件和 setTimeout 中都是同步的;
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
2. 生命周期
在这里只总结一些常用的生命周期函数。
2.1 render
它是class组件必需的方法,获取最新的 props 和 state,在不修改组件 state 的情况下,每次调用时都返回相同的结果。
2.2 constructor
如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。
- 通过给 this.state 赋值对象来初始化内部 state。
- 为事件处理函数绑定实例。
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
1. 不要调用 setState()
2. 避免将 props 的值复制给 state
this.state = { color: props.color }; // wrong
2.3 componentDidMount
会在组件挂载后(插入DOM树中)立即调用。依赖于 DOM 节点的初始化应该放在这里,如需通过网络请求获取数据。可以在此声明周期里加 setState,但发生在浏览器更新屏幕之前,会导致性能问题;
有更新在render阶段的 constructor 中 init State,但有更新可以在此方法时 setState。
2.4 componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
会在更新后被立即调用。首次渲染不会执行此方法。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):加条件判断,不然死循环
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
// 如果组件实现了 getSnapshotBeforeUpdate() 生命周期,
// 则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined。
如果 shouldComponentUpdate() 返回值 为 false,则不会调用componentDidUpdate()。
2.5 componentWillUnmount
componentWillUnmount()会在组件卸载及销毁之前直接调用。例如,清除timer,取消网络请求。
componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。