React思想
始终整体“刷新”页面,无需关心细节。
React特点
- 1个新概念
组件的方式去描述UI
4个必须API
- ReactDOM.render 方法让 React 组件渲染到某个具体的 DOM 节点
- 组件的 render 方法,渲染对应的组件
- 组件的 setState 方法,用于改变组件状态,触发组件的render方法重新渲染组件
- 组件之间可以通过 props 给 React 组件传递参数(数据只能从上往下流动)
单向数据流
- 完善的错误提示
React解决了UI细节问题,数据模型如何解决?
react组件的概念
有属性和状态最终组成一个View。一个组件的状态有2种,一个是外部传过来的属性,一个是内部维护的状态。
所以组件可以理解为一个纯函数,输入的参数一样,渲染的UI结果一定一样。
何时创建组件?单一职责原则
- 每个组件只做一件事
如果组件变得复杂,那么应该拆分成小组件。拆分成小组件有2个好处:
能计算得到的状态就不要单独存储。计算过程的数据不要存state,而是用的时候直接计算。比如,ajax请求的数据,未返回可以根据未返回数据判断是否loading,而不是存一个状态。
- 组件尽量无状态,所需数据通过props获取。让你的组件尽量是一个纯组件,这样可以具备更好的性能,可以更好的被重用。
JSX:不是模板语言,只是一种语法糖
- 在JavaScript代码中直接写HTML标记
- JSX的本质:动态创建组件的语法糖(纯JS语法,并不是模板语言)
- 在JSX中使用表达式
JSX语法约定:自定义组件以大写字母开头
react ^16.3版本
- react ^16.4版本
图片来源:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
上述俩个版本的区别在于getDerivedStateFromProps, 其方法的介绍参考如下文章:https://juejin.cn/post/6844903857550688269
三个阶段
- Render阶段:用于计算一些当前的状态
- Pre-commit阶段:没有真正的更新DOM,但是可以读取DOM节点的内容
Commit阶段:React把当前的状态映射到DOM的时候,需要实际的更新DOM节点
三个类型
创建时
- constructor:一个组件的构造函数,也就是说一个组件更新到界面之前,它需要构造创建出来
- constructor就是js标准的类的构造函数,用于初始化内部的状态,但其实在React内是很少使用构造函数内的方法的,因为React需要做一些初始化的事情可能会在其他的生命周期中去做
- 唯一可以修改state的地方,其他地方修改state都需要调用setState方法
- getDerivedStateFromProps:这个是react^16.3版本新引入的一个API,它用于从外部的属性去初始化内部的一些状态
- 当state需要从props初始化时使用
- 尽量不要使用:维护俩者状态一致性会增加复杂度。因为如果你需要从props获取值,你可以直接计算获取,而不需要在state里面存储,因为一旦存储就需要维护俩者的一致性,会增加复杂度,容易出bug。(题外话:react维护者调侃说,之所以命名这么长就是不推荐大家使用)
- 每次render都会调用(替代原有的componentWillReceiveProps,也是这个机制)
- 典型场景:表单控件获取默认值。表单除了用户输入值外,还会有一个初始的默认值,一旦用户修改之后就不再有用了。一开始的内部state是来自外部的初始值
- 当state需要从props初始化时使用
- render:描述UI的DOM结构的地方,也是组件唯一需要必须定义的一个生命周期方法,因为你需要描述你的UI
- componentDidMount:发起AJax请求,定义一些外部的资源等,做一些副作用的事情
- UI渲染完成后调用:这个时候知道所有的UI渲染完成了,在这里可以安全的操作DOM节点以及触发一些ajax请求,获取一些外部的资源
- 这个方法在整个生命周期中只执行一次,如果你的组件只获取一次外部资源,就在这里执行
- 典型场景:获取外部资源
- UI渲染完成后调用:这个时候知道所有的UI渲染完成了,在这里可以安全的操作DOM节点以及触发一些ajax请求,获取一些外部的资源
- constructor:一个组件的构造函数,也就是说一个组件更新到界面之前,它需要构造创建出来
更新时(发生场景:组件有一个新的属性传进来;内部修改了state,修改内部状态调用setState;调用forceUpdate时,即使内部状态没有发生改变,依然想更新UI,调用setState强制组件刷新)
- getDerivedStateFromProps:这个是react^16.3版本新引入的一个API,它用于从外部的属性去初始化内部的一些状态
- shouldComponentUpdate:告诉组件你是否真的需要render,这是一个用户介入的过程,这里可以做一些性能优化的事情,返回false不需要render,返回true需要render,继续下面的流程
- 决定Virtual DOM是否要重绘
- 一般可以由PureComponent自动实现
- 典型场景:性能优化
- render:计算虚拟dom,虚拟dom用来维持UI的状态,从而进行一些diff计算
- getSnapshotBeforeUpdate:React^16.3引入的API
- 在页面render之前调用,state已经更新
- 典型场景:获取render之前的DOM状态
- 在页面render之前调用,state已经更新
- React更新DOM和refs(引用):这个周期不需要我们关心
- componentDidUpdate
- 每次UI更新时被调用
- 典型场景:页面需要props变化重新获取数据
- 每次UI更新时被调用
- getDerivedStateFromProps:这个是react^16.3版本新引入的一个API,它用于从外部的属性去初始化内部的一些状态
- 卸载时
- componentWillUnmount
- 组件移除时被调用
- 典型场景:资源释放
- 组件移除时被调用
- componentWillUnmount
React基础
理解Virtual DOM及key属性的作用
JSX的运行基础:Virtual DOM
计算2棵树的差异,标准运算的复杂度是o(n)^3,运算量比较大。这么搞得复杂度,性能不满足UI的性能需求。
Facebook工程师做了一个算法的优化,复杂度降到了o(n),完全变成了线性的算法。
拿到前后俩个状态的DOM树之后,一层一层的进行比较。
组件设计模式:高阶组件和函数组件作为子组件
- 高阶组件
- 对已有组件进行封装,形成一个新的组件。新的组件会包含一些自己的应用逻辑,这个逻辑会产生一些新的状态,这个状态会传给已有的组件
- 高阶组件一般不会有自己的UI展现,只是为它封装的组件提供一些额外的功能或者数据
请注意,HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。
withTimer.js
export default function withTimer(WrappedComponent) {
return class extends React.Component {
state = {time: new Date()};
componentDidMount() {
this.timerID = setInterval(()=>this.tick(),1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
time: new Date()
});
}
render() {
return <WrappedComponent time={this.state.time} {...props} />;
}
}
}
ChatApp.js
import React from "react"
import withTimer from "../withTimer"
class MessageList extends React.PureComponent {
render() {
return <ul>{this.props.messages.map(msg => <li>{msg}</li>)}</ul>;
}
}
export class ChatApp extends React.Component {
state = {
messages: [],
inputMsg: "",
};
handleInput = evt => {
this.setState({
inputMsg: evt.target.value,
});
};
handleSend = () => {
const text = this.state.inputMsg;
if (text) {
const newMessages = [...this.state.messages, text];
this.setState({
messages: newMessages,
inputMsg: "",
});
}
};
render() {
return (
<div>
<MessageList messages={this.state.messages} />
<div>
<input value={this.state.inputMsg} />
<button onClick={this.handleSend}>Send</button>
</div>
<h2>{this.props.time.toLocaleString()}</h2>
</div>
);
}
}
export default withTimer(ChatApp);
注意上文代码里的state并没有放到constructor里面,因为messages和inputMsg是不需要通过初始化赋值。
实例的属性,变量初始值需要在new的时候初始化,就放在constructor里面。
class People {
age =0 ;
constructor(name) {
this.name = name;
}
}
const Erina = new People('erina');
知识扩展:
- 函数作为子组件
- 新的设计模式,不是react自身的特性
- 下图代码,不是把一个节点作为children,而是把一个函数作为children
“函数作为子组件” 是一个组件,这个组件接收一个函数作为其子组件。由于 React 的属性(property) 类型,该模式可以简单地实现并且值得推广。
JavaScript代码:
class MyComponent extends React.Component {
render() {
return (
<div>
{this.props.children('erina')}
</div>
)
}
}
MyComponent.propTypes = {
children: React.PropTypes.func.isRequired
};
注: 从 React v15.5 开始 ,React.PropTypes 助手函数已被弃用,我们建议使用 prop-types 库 来定义contextTypes。 即你需要手动引入 import PropTypes from 'prop-types';
这就函数作为子组件!通过使用函数作为子组件,我们将父组件和子组件分离,让使用者决定如何将参数应用于子组件。例如:
JavaScript 代码:
<MyCompnent>
{(name) => (
<div>{name}</div>
)}
</MyCompnent>