文档链接
- 英文官网:https://reactjs.org/
- React(印记中文):https://react.docschina.org/
- React中文网:https://reactjs.org.cn/
- React-router(印记中文):https://react-router.docschina.org/
- creat-react-app(印记中文):https://cra.docschina.org/
- React Training仓库:https://github.com/ReactTraining
- 码云仓库:https://gitee.com/mirrors/react
- 菜鸟教程:https://www.runoob.com/react/react-tutorial.html
- https://hulufei.gitbooks.io/react-tutorial/content/introduction.html
什么是React
React 是一个用于构建用户界面的JavaScript库,核心专注于视图,目的是实现组件化开发。
组件化的概念
React 应用都是构建在组件之上。
我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式,再将这些独立组件组合完成一个复杂的界面。这样既减少了逻辑复杂度,又实现了代码的重用。
- 可组合:一个组件可以和其它的组件一起使用或者可以直接嵌套在另一个组件内部
- 可重用:每个组件都是具有独立功能的,它可以被使用在多个场景中
- 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护
搭建React开发环境
cnpm i -g create-react-app
create-react-app 01.basic
npx create-react-app my-app # 如果安装失败,把yarn卸载掉
cd 01.basic
npm run start
Virtual DOM
当组件状态 state
有更改的时候,React 会自动调用组件的 render
方法重新渲染整个组件的 UI。
当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实现了一个Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM 上,React 在这个 Virtual DOM 上实现了一个 diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。
JSX
什么是jsx
一种js和html混合的语法,将组件的结构、数据甚至样式都聚合在一起定义组件
import React from 'react';
import ReactDOM from 'react-dom';
// render 方法负责把虚拟DOM变成真实的DOM插入到容器里
ReactDOM.render(
<h1>hello</h1>, //这就是React元素
document.getElementById('root')
);
什么是元素
- JSX其实只是一种语法糖,最终会通过
babeljs
转译成createElement
语法 - React元素是构成
React
应用的最小单位 - React元素用来描述你在屏幕上看到的内容
- React元素是React虚拟DOM元素
- React元素事实上是普通的JS对象,ReactDOM来确保浏览器中的DOM数据和React元素保持一致
```javascript // babel 会把上面的jsx语法编译为下面这样 let element2 = React.createElement(‘h1’, { className: ‘title’, style: {color: ‘red’}, }, ‘hello’); //1个儿子 // }, ‘hello’, ‘world’); //2个儿子let element1 = <h1 className="title" style={{color:'red'}}>hello</h1>
console.log(element1);
`createElement` 的结果就是 虚拟DOM
```javascript
{
type:'h1',
props:{
className: "title",
style: {color: 'red'},
},
children: "hello", //1个儿子
//children: ['hello', 'world'], //多个儿子,就会以数组形式返回
}
JSX表达式
可以任意地在 JSX 当中使用js表达式,在 JSX 当中的表达式要包含在大括号里
let title = 'hello';
let element = <h1>{title}</h1>;
JSX属性
- JSX 并不是
html
,更像JavaScript
在 JSX 中属性不能包含关键字
可以在
if
或者for
语句里使用JSX
- 可以将它赋值给变量,当做参数传入,作为返回值都可以
if中使用
import React from 'react';
import ReactDOM from 'react-dom';
function greeting(name) {
if (name) {
return <h1>Hello {name}</h1>;
}
return <h1>Hello world</h1>;
}
const element = greeting('cy');
ReactDOM.render(element, document.getElementById('root'));
for、map中使用
import React from 'react';
import ReactDOM from 'react-dom';
let names = ['张三', '李四', '王五'];
// let elements = [];
// for (let i=0; i<names.length; i++){
// elements.push(<li key={i}>{names[i]}</li>)
// }
let elements = names.map((item, index) => <li key={index}>{item}</li>)
const element = <ul>{elements}</ul>;
ReactDOM.render(element, document.getElementById('root'));
更新元素渲染
- React 元素都是
immutable
不可变的。当元素被创建之后,你是无法改变其内容和属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一个时间点的样子。 - 更新界面的唯一方法是创建一个新的元素,然后将它传入
ReactDOM.render()
方法。 ```javascript import React from ‘react’; import ReactDOM from ‘react-dom’;
const getElement= (name) =>
function tick() { const element = (
<a name="cUeUI"></a>
##### React渲染时只会更新必要的部分
- React DOM 首先会把老的虚拟DOM和新的虚拟DOM进行比较,也就是所谓的 dom-diff
- 找到它们之间的差异,然后在渲染过程中只会更新改变了的部分。
- 即便我们每秒都创建了一个描述整个UI树的新元素,React DOM 也只会更新渲染文本节点中发生变化的内容。
<a name="L6vNB"></a>
# 组件 & Props
- 可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件
- 组件从概念上类似与 JavaScript 函数。它接受任意的入参(即 "props"),并返回用于描述页面展示内容的React元素
<a name="ioHiO"></a>
## React.Fragment 空容器组件
等同于 `<></>` ,其经过 React.createElement 处理后,type 等于 `Symbol(react.fragment)`<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/139415/1616402787364-e603745e-ed58-40e9-8195-51d6a994abe7.png#height=128&id=MJzjt&margin=%5Bobject%20Object%5D&name=image.png&originHeight=128&originWidth=258&originalType=binary&ratio=1&size=11183&status=done&style=stroke&width=258)
```javascript
class Form extends React.Component {
constructor(props){
super(props);
}
render(){
return (
<React.Fragment>
<div className="a">hello</div>
<span>world</span>
<><p>today</p></>
</React.Fragment>
)
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
自定义函数组件
- 函数组件接收一个单一的
props
对象并返回了一个React元素。 - 函数组件打印出来,其type就是函数本身 ```javascript import React from ‘react’; import ReactDOM from ‘react-dom’;
function App(props){ return (
console.log(element); //函数组件打印出来,其type就是函数本身 ReactDOM.render(element, document.getElementById(‘root’));
![image.png](https://cdn.nlark.com/yuque/0/2021/png/139415/1616125605876-40a7c004-cf65-4c65-ba4e-c6575faf4644.png#height=243&id=OJtUh&margin=%5Bobject%20Object%5D&name=image.png&originHeight=243&originWidth=474&originalType=binary&ratio=1&size=25994&status=done&style=stroke&width=474)
<a name="G1pru"></a>
## 自定义类组件
- 类组件打印出来,其type就是类组件本身
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
const root = document.getElementById('root');
class App extends React.Component {
render(){
return <div>hello, {this.props.name}</div>
}
}
let element = <App name="hello">world</App>;
console.log(element); //类组件打印出来,其type就是类组件本身
ReactDOM.render(element, document.getElementById('root'));
组件渲染
- React元素不但可以是DOM标签,还可以是是用户自定义的组件
- 当 React 元素为用户自定义组件时,它会将JSX所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为
props
- 组件名称必须以大写字母开头
- 组件必须在使用的时候定义或引用它
- 组件的返回值只能有一个根元素
状态
- 组件的数据来源有两个地方,分别是属性对象props和状态对象state
- 属性是父组件传递过来的(默认属性,属性校验)
- 状态是自己内部的,改变状态唯一的方式就是
setState
- 属性和状态的变化都会影响视图更新 ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’;
class Counter extends React.Component { constructor(props){ super(props); this.state = { name: props.name, number: 0, } } handleClick = () => { this.setState({ number: this.state.number+1, }) } render(){ return (
${this.props.name}: ${this.state.number}
}let element =
<a name="3g1VE"></a>
##### 不要直接修改state
构造函数是唯一可以给 `this.state` 赋值的地方,其它地方一律用 `setState` 去更新
```javascript
onClick = () => {
this.setState({ number: this.state.number + 1 });
//this.state.number = this.state.number + 1; //不要这样做
}
合成事件和批量更新
- 在 React 里,事件的更新可能是异步的,是批量的,不是同步的。
- 出于性能的考虑,React 在调用
setState
之后,状态并没有立刻更新,而是先缓存起来,等事件函数处理完成后,再进行批量更新,即把多个setState
调用合并成一个调用,一次更新并重新渲染。 - 因为
this.props
和this.state
可能会异步更新,所以不要依赖他们的值来更新下一个状态。
只要在react管理控制的就是批量更新;只要不归react管了,就是非批量。
- jsx事件处理函数、生命周期函数中就是批量的。这些函数的执行发生在React内部,React对它们有完全的控制权。函数执行前,react会把一个变量
canMerge
的置为true,当函数执行完后,又会把canMerge
置为 false。当其为true时,react会把函数里每一次 setState 存起来,比如存到finaleState
里,先不更新,当其变为false之后,会手动进行一次更新,把这些存起来的一起进行批量更新。 setTimeout、Promise 这种都不归react管,就是非批量的。React对这些没有控制权。
handleClick = () => {
//这里会批量更新
this.setState({number: this.state.number+1 });
console.log(this.state.number); //0
this.setState({number: this.state.number+1 });
console.log(this.state.number); //0
// 这里不论放在宏任务,还是微任务里,结果都一样。都不会批量更新
// 因为 canMerge变为false,且上面的批量更新走完之后,才会开始走微任务。
// React 对setTimeout 并没有控制权,那么它里面的不能批量更新,有几次就更新几次。
//Promise.resolve().then(() => {
setTimeout(() => {
console.log(this.state.number); //1
this.setState({number: this.state.number+1 });
console.log(this.state.number); //2
this.setState({number: this.state.number+1 });
console.log(this.state.number); //3
})
}
那么setTimeout里面也想要实现批量更新,怎么办?**unstable_batchedUpdates**
class A extends React.Component {
handleClick = () => {
this.setState({x: 1})
this.setState({x: 2})
this.setState({x: 3})
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
this.setState({x: 4})
this.setState({x: 5})
this.setState({x: 6})
})
}, 0)
}
render() {
return (<div onClick={this.handleClick}></div>
}
}
这个api的实现就类似下面这样:
function unstable_batchedUpdates(fn) {
this.canMerge = true
fn()
this.canMerge = false
const finalState = ... //通过this.updateQueue合并出finalState,然后批量更新
this.setState(finaleState)
}
那么如果主要想让state基于上一状态,怎么做呢?
答:可以让 setState()
接收一个函数。函数第一个参数就是上一状态的state对象,函数的第2个参数,就是状态变化后的回调函数 callback
。
注意:虽然函数参数是基于上一状态的state。但还是批量更新。因此下面打印出来仍是 0 0
handleClick = () => {
this.setState(prevState => ({number: prevState.number + 1}), () => {
//注意等全部批量更新完成后,回调才发生。所以这里是2
console.log('callback', this.state.number); //2
});
console.log(this.state.number); //0
this.setState(prevState => ({number: prevState.number + 1}));
console.log(this.state.number); //0
setTimeout(() => {
console.log(this.state.number); //2
this.setState(prevState => ({number: prevState.number + 1}));
console.log(this.state.number); //3
this.setState(prevState => ({number: prevState.number + 1}));
console.log(this.state.number); //4
}, 1000)
}
forceUpdate
forceUpdate从函数名上理解:“强制更新”。 既然是“强制更新”有两个问题容易引起误解:
- forceUpdate 是同步的吗?“强制”会保证调用然后直接dom-diff吗?
- “强制”更新整个组件树吗?包括自己,子孙后代组件吗?
对于第一个问题:forceUpdate在批量与否的表现上,和setState是一样的。在React有控制权的函数里,是批量的。class A extends React.Component{
handleClick = () => {
this.forceUpdate()
this.forceUpdate()
this.forceUpdate()
this.forceUpdate()
}
shouldComponentUpdate() {
return false
}
render() {
return (
<div onClick={this.handleClick}>
<Son/> // 一个组件
</div>
)
}
}
对于第二个问题:forceUpdate只会强制本身组件的更新,即不调用“shouldComponentUpdate”直接更新,对于子孙后代组件还是要调用自己的“shouldComponentUpdate”来决定的。
所以forceUpdate 可以简单的理解为 this.setState({}),只不过这个setState 是不调用自己的“shouldComponentUpdate”声明周期的。
事件处理
- React 的命名采用小驼峰式(camelCase),而不是纯小写
- 使用 JSX 语法时,你需要传入一个函数作为事件处理函数,而不是一个字符串
- 你不能通过返回
false
的方式阻止默认行为。你必须显示的使用preventDefault
```jsx import React from ‘react’; import ReactDOM from ‘react-dom’;
class Counter extends React.Component { constructor(props){ super(props); this.state = { name: props.name, number: 0, } } handleClick = (e) => { e.stopPropagation(); //阻止冒泡 } aa = (e) => { console.log(‘aa’); } bb = (e) => { e.preventDefault(); //阻止默认行为 } render(){ return (
${this.props.name}: ${this.state.number}
}let element =
```javascript
//阻止默认行为
export function prevent(event){
var e = event || window.event;
if (typeof e.preventDefault !== 'undefined'){ //W3C
e.preventDefault();
} else { //IE
e.returnValue = false;
}
}
//阻止冒泡
export function stopPro(event) {
var e = event || window.event;
if (typeof e.stopPropagation !== 'undefined') {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
if (e.nativeEvent){
e.nativeEvent.stopImmediatePropagation();
}
}
this 与 事件函数传参
this
你必须谨慎对待 JSX 回调函数中的 this,可以使用:
匿名函数(fn4)
- bind(fn5) ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’;
class Counter extends React.Component { constructor(props){ super(props); this.fn3 = this.fn3.bind(this); } fn1(){ console.log(‘fn1’, this); //undefined } fn2 = () => { console.log(‘fn2’, this); //Counter实例 } fn3(){ console.log(‘fn3’, this); //Counter实例 } fn4(…args){ console.log(‘fn4’, this, args); //Counter实例 [“1”, 2, SyntheticBaseEvent] } fn5(…args){ console.log(‘fn5’, this, args); //Counter实例 [“1”, 2, SyntheticBaseEvent] } render(){ return (
let element =
<a name="2j6EY"></a>
## Ref
- Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
- 在 React 渲染声明周期时,表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。在这种情况下,你可以指定一个 defaultValue 属性,而不是 value
<a name="d8Mz0"></a>
### 为 DOM 元素添加 ref
<a name="VjRqF"></a>
##### 方式1:(已废弃)
```jsx
<input ref="refInput"></TextInput>
console.log(this.refs.refInput); //input元素的DOM节点
方式2:
<input ref={(node) = {this.refInput = node}></TextInput>
console.log(this.refInput); //input元素的DOM节点
方式3:
- 可以用 ref 去存储 DOM 节点的引用
- 当 ref 属性用于 HTML 元素时,构造函数中使用
React.createRef()
创建的ref 接收底层的 DOM 元素作为其current
属性
import React from 'react';
import ReactDOM from 'react-dom';
class Sum extends React.Component {
a;
b;
result;
constructor(props){
super(props);
this.a = React.createRef();
this.b = React.createRef();
this.result = React.createRef();
}
handleAdd = () => {
let aValue = this.a.current.value; //this.a.current 就等于 ref={this.a} 的那个DOM元素
let bValue = this.b.current.value;
this.result.current.value = aValue + bValue;
}
render(){
return (
<div>
<input type="text" ref={this.a} />加
<input type="text" ref={this.b} />
<button onClick={this.handleAdd}>等于</button>
<input type="text" ref={this.result} />
</div>
)
}
}
ReactDOM.render(<Sum />, document.getElementById('root'));
为 class 组件添加 Ref
- 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性 ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’;
class Form extends React.Component {
refTextInput;
constructor(props){
super(props);
this.refTextInput = React.createRef();
}
getFocus = () => {
let refTextInput = this.refTextInput.current; //TextInput组件实例
refTextInput.getFocus();
}
render(){
return (
<>
class TextInput extends React.Component { refInput; constructor(props){ super(props); this.refInput = React.createRef(); } getFocus = () => { this.refInput.current.focus(); } render(){ return } }
ReactDOM.render(
, document.getElementById(‘root’));
<a name="0WQxR"></a>
### Ref 转发
- 不能在函数组件上使用 ref 属性,因为他们没有实例
- Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧
- Ref 转发允许某些组件接收 ref,并将其向下传递(换句话说,转发“它”)给子组件
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
const TextInput = React.forwardRef((props, ref) => {
return <input ref={ref} />
})
class Form extends React.Component {
refTextInput;
constructor(props){
super(props);
this.refTextInput = React.createRef();
}
getFocus = () => {
let refTextInput = this.refTextInput.current; //TextInput函数组件里的 input节点
refTextInput.focus();
}
render(){
return (
<>
<TextInput ref={this.refTextInput}></TextInput>
<button onClick={this.getFocus}>获得焦点</button>
</>
)
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
生命周期
旧版生命周期
- 一般DidMount、自定义时间处理函数可以用setState,
- 其它里面尽量不要用setState,很有可能会死循环
- forceUpdate() 强制刷新组件
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
static defaultProps = { //设置初始属性对象
name: '计数器',
}
constructor(props){
super(props);
this.state = { number: 0 };
console.log('Counter 1.constructor 初始化属性和状态对象');
}
componentWillMount(){
console.log('Counter 2.componentWillMount 组件将要挂载');
}
componentDidMount(){
console.log('Counter 4.componentDidMount 组件完成挂载');
}
handleClick = () => {
console.log('click add button!');
this.setState({number: this.state.number + 1}, () =>{
console.log(this.state);
});
}
// react可以在shouldComponentUpdate方法中优化,PureComponent 可以帮我们做这件事
shouldComponentUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
console.log('Counter 5.shouldComponentUpdate 决定组件是否需要更新?');
return nextState.number % 2 === 0; //此函数只有返回了true,才会调用 render 方法
}
componentWillUpdate(nextProps, nextState){ //参数:下一次属性;下一次状态
console.log('Counter 6.componentWillUpdate 组件将要更新');
}
componentDidUpdate(prevProps, prevState){ //参数:上一次属性;上一次状态
console.log('Counter 7.componentDidUpdate 组件完成更新', prevProps, prevState);
}
render(){
console.log('Counter 3.render 重新计算新的虚拟DOM');
let st = this.state;
return (
<div id={`counter-${st.number}`} >
<p>{st.number}</p>
{st.number === 4 ? null : <ChildCounter count={st.number} />}
<button onClick={this.handleClick}>+</button>
<FunctionCounter count={st.number} />
</div>
)
}
}
class ChildCounter extends React.Component {
componentWillUnmount(){
console.log('ChildCounter 8.componentWillUnmount 组件将要卸载');
}
componentWillMount(){
console.log('ChildCounter 1.componentWillMount 组件将要挂载');
}
componentDidMount(){
console.log('ChildCounter 3.componentDidMount 组件完成挂载');
}
// 第一次不会执行,之后属性更新时才会执行(即:组件挂载时不执行,组件挂载后,有属性变化,才执行)
componentWillReceiveProps(newProps){
console.log('ChildCounter 4.componentWillReceiveProps 组件将要接收到新的属性');
}
shouldComponentUpdate(nextProps, nextState){
console.log('ChildCounter 5.shouldComponentUpdate 决定组件是否需要更新?');
return nextProps.count % 3 === 0; //3的倍数就更新,否则就不更新
}
componentWillUpdate(){
console.log('ChildCounter 6.componentWillUpdate 组件将要更新');
}
componentDidUpdate(){
console.log('ChildCounter 7.componentDidUpdate 组件完成更新');
}
render(){
console.log('ChildCounter 2.render');
return (
<div id="sub-counter">{`ChildCounter: ${this.props.count}`}</div>
)
}
}
let FunctionCounter = (props) => <div id="counter-function">{props.count}</div>
ReactDOM.render(<Counter />, document.getElementById('root'));
// Counter 1.constructor 初始化属性和状态对象
// Counter 2.componentWillMount 组件将要挂载
// Counter 3.render 重新计算新的虚拟DOM
// ChildCounter 1.componentWillMount 组件将要挂载
// ChildCounter 2.render
// ChildCounter 3.componentDidMount 组件完成挂载
// Counter 4.componentDidMount 组件完成挂载
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// {number: 1}
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// Counter 6.componentWillUpdate 组件将要更新
// Counter 3.render 重新计算新的虚拟DOM
// ChildCounter 4.componentWillReceiveProps 组件将要接收到新的属性
// ChildCounter 5.shouldComponentUpdate 决定组件是否需要更新?
// Counter 7.componentDidUpdate 组件完成更新 {name: "计数器"} {number: 1}
// {number: 2}
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// {number: 3}
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// Counter 6.componentWillUpdate 组件将要更新
// Counter 3.render 重新计算新的虚拟DOM
// ChildCounter 8.componentWillUnmount 组件将要卸载
// Counter 7.componentDidUpdate 组件完成更新 {name: "计数器"} {number: 3}
// {number: 4}
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// {number: 5}
// click add button!
// Counter 5.shouldComponentUpdate 决定组件是否需要更新?
// Counter 6.componentWillUpdate 组件将要更新
// Counter 3.render 重新计算新的虚拟DOM
// ChildCounter 1.componentWillMount 组件将要挂载
// ChildCounter 2.render
// ChildCounter 3.componentDidMount 组件完成挂载
// Counter 7.componentDidUpdate 组件完成更新 {name: "计数器"} {number: 5}
// {number: 6}
新版生命周期
getDerivedStateFromProps
static getDerivedStateFromProps(nextProps, prevState)
功能实际上就是将传入的props映射到state上面。- 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回
null
则不更新任何内容。 - 该函数是静态的,只是一个 pure function 映射函数,可以认为它是一个纯函数。跟实例没有关系,也不能访问this
- 里面不能调用
this.setState
避免出现死循环。 - 单例属性肯定要比实例属性节约资源
- 注意:不管原因是什么,都会在每次渲染前触发此方法。这与
UNSAFE_componentWillReceiveProps
形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用setState
时。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Counter extends Component {
constructor(props){
super(props);
this.state = { number: 0 };
console.log('Counter-constructor:初始化属性和状态');
}
componentDidMount(){
console.log('Counter-componentDidMount:组件完成挂载');
}
shouldComponentUpdate(nextProps, nextState){
console.log('Counter-shouldComponentUpdate:是否要更新?');
return true;
}
componentDidUpdate(prevProps, prevState){
console.log('Counter-componentDidUpdate:组件完成更新');
}
handleClick = () => {
console.warn('click add button!');
this.setState({number: this.state.number + 1});
}
render(){
let st = this.state;
console.log('Counter-render:渲染', st);
return (
<div id={`counter-${st.number}`} >
<p>{st.number}</p>
<ChildCounter count={st.number} />
<button onClick={this.handleClick}>+</button>
</div>
)
}
}
class ChildCounter extends Component {
constructor(props){
super(props);
this.state = {name: 'jack', number: 0};
console.log('ChildCounter-constructor:初始化属性和状态');
}
/** 从组件的新属性中映射出一个状态,类似以前的 componentWillReceiveProps
*
* @param {*} nextProps
* @param {*} nextState
* @returns
*/
static getDerivedStateFromProps(nextProps, prevState){
console.log('ChildCounter-getDerivedStateFromProps', nextProps, prevState);
const { count } = nextProps;
if (count%2 === 0){
return {number: count*2}; //返回分状态对象,会和自己的state进行合并。类似setState
} else if (count%3 === 0){
return {number: count*3};
}
return null; //返回null,表示不修改状态
}
componentDidMount(){
console.log('ChildCounter-componentDidMount');
}
componentDidUpdate(prevProps, prevState){
console.log('ChildCounter-componentDidUpdate');
}
render(){
let st = this.state;
return (<div>{`${st.name}: ${st.number}`}</div>)
}
}
ReactDOM.render(<Counter />, document.getElementById('root'));
// Counter-constructor:初始化属性和状态
// Counter-render:渲染 {number: 0}
// ChildCounter-constructor:初始化属性和状态
// ChildCounter-getDerivedStateFromProps {count: 0} {name: "jack", number: 0}
// ChildCounter-componentDidMount
// Counter-componentDidMount:组件完成挂载
// click add button!
// Counter-shouldComponentUpdate:是否要更新?
// Counter-render:渲染 {number: 1}
// ChildCounter-getDerivedStateFromProps {count: 1} {name: "jack", number: 0}
// ChildCounter-componentDidUpdate
// Counter-componentDidUpdate:组件完成更新
// click add button!
// Counter-shouldComponentUpdate:是否要更新?
// Counter-render:渲染 {number: 2}
// ChildCounter-getDerivedStateFromProps {count: 2} {name: "jack", number: 0}
// ChildCounter-componentDidUpdate
// Counter-componentDidUpdate:组件完成更新
getSnapshotBeforeUpdate
- 被调用于render之后,可以读取但无法使用DOM的时候。
- 它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。
- 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。 ```javascript import React, { Component } from ‘react’; import ReactDOM from ‘react-dom’;
class Counter extends Component { refUl = React.createRef(); state = { list: [], } // 被调用于render之后,可以读取但无法使用DOM的时候。 // 它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。 // 此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。 getSnapshotBeforeUpdate(){ return this.refUl.current.scrollHeight; // 拿到ul的真实dom,获取它的内容高度(渲染前) } componentDidUpdate(prevProps, prevState, prevScrollHeight){ let currentHeight = this.refUl.current.scrollHeight; //ul当前的内容高度(渲染后) console.log(‘本次ul增加的高度’, currentHeight - prevScrollHeight); } handleClick = () => { let list = this.state.list; list.push(list.length); this.setState({list}); } render(){ return (
-
{this.state.list.map((item, index) => (
- {index} ))}
<a name="iwUjw"></a>
# Context 上下文
- 在某些场景下,你想在整个组件树中传递数据,但却不想手动地在每一层传递属性。你可以直接在 React 中使用强大的 `context` API 解决上述问题
- 在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好、UI 主题)。这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props
![sdre.gif](https://cdn.nlark.com/yuque/0/2021/gif/139415/1617292570662-2f845924-a679-4e77-842b-378d5d817e49.gif#height=224&id=V1MB6&margin=%5Bobject%20Object%5D&name=sdre.gif&originHeight=224&originWidth=428&originalType=binary&ratio=1&size=49858&status=done&style=stroke&width=428)
<a name="jf9mB"></a>
##### 使用
```jsx
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
let PersonContext = React.createContext(); //{Provider 提供者, Consumer 消费者}
let BodyContext = React.createContext();
const getStyle = color => ({ width: '300px', border: `3px solid ${color}`, padding: '5px' });
class Person extends Component {
state = {color: 'red'}
changeColor = (color) => this.setState({color})
render(){
let contextValue = {name: 'Person', color: this.state.color, changeColor: this.changeColor};
return(
<PersonContext.Provider value={contextValue}>
<div style={getStyle(this.state.color)}>
Person
<Head />
</div>
</PersonContext.Provider>
)
}
}
// 子组件 使用方法1:
class Head extends Component{
// 给要使用的子组件,加上静态属性 contextType
// 之后就可以用 this.context 去获取 PersonContext.Provider组件 value属性 的值 contextValue
static contextType = PersonContext;
render(){
return (
<div style={getStyle(this.context.color)}>
Head
<Eye />
<button onClick={() => this.context.changeColor('red')}>变红</button>
<button onClick={() => this.context.changeColor('green')}>变绿</button>
</div>
)
}
}
// 子组件 使用方法2:
function Eye(props){
return (
<PersonContext.Consumer>
{value => (
<div style={getStyle(value.color)}>Eye</div>
)}
</PersonContext.Consumer>
)
}
ReactDOM.render(<Person />, document.getElementById('root'));
多层 Provider 嵌套
- Provider 也可以嵌套 Provider
如果父组件有多层 Provider,子组件可以使用指定的 Provider
class Body extends Component{
render(){
let contextValue = {name: 'Body'};
return (
<BodyContext.Provider value={contextValue}>
<div >
Body
<Hand />
</div>
</BodyContext.Provider>
)
}
}
class Hand extends Component {
static contextType = PersonContext;
// static contextType = BodyContext; //使用指定的Provider
render(){
return (
<div style={getStyle(this.context.color)}>
{`${this.context.name} -- Hand`}
<button onClick={() => this.context.changeColor('red')}>变红</button>
<button onClick={() => this.context.changeColor('green')}>变绿</button>
</div>
)
}
}
高阶组件
https://www.yuque.com/zhuchaoyang/wrif6k/we9kxl
- 高阶组件就是一个函数,接收 React 组件作为输入,输出一个新的 React 组件。
高阶组件让代码更具有复用性、逻辑性与抽象特征。可以对 render 方法作劫持,也可以控制 props 与 state。
const NewComponent = higherOrderComponent(OldComponent)
高阶组件的用法主要有如下两种:
属性代理(props proxy)。属性组件通过被包裹的 React 组件来操作 props。
- 反向代理(inheritance inversion)。高阶组件继承于被包裹的 React 组件。
creat-react-app 支持装饰器配置
安装依赖
cnpm i -D react-app-rewired customize-cra @babel/plugin-proposal-decorators
修改package.json
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
}
config-overrides.js 扩展配置
const {override,addBabelPlugin} = require('customize-cra');
module.exports = override(
addBabelPlugin( [
"@babel/plugin-proposal-decorators", { "legacy": true }
])
)
jsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true, //启用装饰器
}
}
属性代理
- 基本属性代理:操作组件的 props
- 在 render 方法中返回传入 WrappedComponent 的 React 组件。这样就可以通过高阶组件来传递 props,这种方法即为属性代理。 ```jsx import React, { Component } from ‘react’; import ReactDOM from ‘react-dom’;
const withLoading = message => OldComponent => {
return class NewComponent extends Component {
show = () => {
let div = document.createElement(‘div’);
div.innerHTML = <p
id="loading"
style="position: absolute; top: 50%; left: 50%;">${message}</p>
;
document.body.appendChild(div);
}
hide = () => {
document.getElementById(‘loading’).remove();
}
render(){
let extraProps = {show: this.show, hide: this.hide};
return
@withLoading(‘加载中…’) class Hello extends Component { render(){ return (
hello
ReactDOM.render(
<a name="r3Q2l"></a>
##### 原始组件想要具备高阶组件对它的修饰,有两种方式。
- 方式一:
```javascript
import MyContainer from './MyContainer.js';
class MyComponent extends Component {
}
export default MyContainer(MyComponent);
- 方式二:
ES7 添加了 decorator 的属性,我们可以通过 decorator 来转换,以此简化高阶组件的调用。
import MyContainer from './MyContainer.js';
@MyContainer
class MyComponent extends Component {
}
export default MyComponent;
这样组件就可以一层层地作为参数被调用,原始组件就具备了高阶组件对它的修饰。这样保持了单个组件封装性的同时还保留了易用性。
执行顺序:先从外到内进入,然后由内向外执行。
https://www.yuque.com/zhuchaoyang/uugnl6/cybthn#KssBh
生命周期:
didmount -> HOC didmount -> (HOCs didmount) ->(HOCs will unmount) -> Hoc will unmount -> unmount
反向继承
- 基于反向继承:拦截声明周期、控制state、渲染过程
高阶组件返回的组件继承于 WrappedComponent。因为被动地继承了 WrappedComponent,所有的调用都会反向。const MyContainer = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
export default MyContainer;
通过继承 WrappedComponent 来实现,方法可以通过 super 来顺序调用。因为依赖于继承的机制,HOC 的调用顺序和队列是一样的:
didmount -> HOC didmount -> (HOCs didmount) -> will unmount -> HOC will unmount -> (HOCs will unmount)
在反向继承方法中,高阶组件可以使用 WrappedComponent 引用,这意味着它可以使用 WrappedComponent 的 state、props、生命周期和render方法,但他不能保证完整的子组件树被解析。
示例:为父类 Button 扩展儿子
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Button extends Component {
state = {name: '张三'}
componentWillMount(){
console.log('Button componentWillMount');
}
componentDidMount(){
console.log('Button componentDidMount');
}
render(){
console.log('Button render');
return <button name={this.state.name} title={this.props.title} />
}
}
// 一般來说,子类继承父类,先调用父类的构造函数,再调子类的构造函数。但这里反过来了
const wrap = Button => {
return class WrapButton extends Button{
// 高阶组件可以读取、修改或删除 父类实例 中的 state,如果需要的话,也可以增加 state。
// 但是这样做,可能让 父类组件的内部状态 变得一团糟。
// 大部分的高阶组件都应该限制读取或增加 state,尤其是后者,可以通过重新命名 state,以防止混淆。
state = {...this.state, number: 0};
componentWillMount(){
console.log('WrapButton componentWillMount');
// super作为对象使用,指向父类的原型对象。
// 通过 super 调用父类方法时,方法内部的 this 指向当前的子类实例
super.componentWillMount();
}
componentDidMount(){
console.log('WrapButton componentDidMount');
super.componentDidMount();
}
add = () => {
this.setState({number: this.state.number + 1})
}
render(){
console.log('WrapButton render');
let superRenderElement = super.render();
// 参数分别是:要克隆的元素; 新属性; 儿子
let renderElement = React.cloneElement(superRenderElement, {
onClick: this.add,
}, `${this.state.name}:${this.state.number}`)
return renderElement;
}
}
}
const WrapButton = wrap(Button);
ReactDOM.render(<WrapButton title="标题" />, document.getElementById('root'));
// WrapButton componentWillMount
// Button componentWillMount
// WrapButton render
// Button render
// WrapButton componentDidMount
// Button componentDidMount
render props
- render-props
render prop
是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术- 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑
- render prop 是一个用于告知组件需要渲染什么内容的函数 prop
- 这也是逻辑复用的一种方式
原生实现
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends Component {
constructor(props){
super(props);
this.state = {x: 0, y:0}
}
handleMouseMove = (e) => {
let {clientX: x, clientY: y} = e;
this.setState({x, y});
}
render(){
return (
<div onMouseMove={this.handleMouseMove}>
<div>
<h1>移动鼠标</h1>
<p>{`当期的鼠标位置 x:${props.x} y:${props.y}`}</p>
</div>
</div>
)
}
}
ReactDOM.render(<MouseTracker />, document.getElementById('root'));
children
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends Component {
constructor(props){
super(props);
this.state = {x: 0, y:0}
}
handleMouseMove = (e) => {
let {clientX: x, clientY: y} = e;
this.setState({x, y});
}
render(){
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.children(this.state)}
</div>
)
}
}
ReactDOM.render(<MouseTracker>
{props => (
<div>
<h1>移动鼠标</h1>
<p>{`当期的鼠标位置 x:${props.x} y:${props.y}`}</p>
</div>
)}
</MouseTracker>, document.getElementById('root'));
render
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MouseTracker extends Component {
constructor(props){
super(props);
this.state = {x: 0, y:0}
}
handleMouseMove = (e) => {
let {clientX: x, clientY: y} = e;
this.setState({x, y});
}
render(){
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
)
}
}
ReactDOM.render(<MouseTracker render={props => (
<div>
<h1>移动鼠标</h1>
<p>{`当期的鼠标位置 x:${props.x} y:${props.y}`}</p>
</div>
)}></MouseTracker>, document.getElementById('root'));
hoc
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
function withTracker(OldComponent){
return class extends Component{
constructor(props){
super(props);
this.state = {x: 0, y:0}
}
handleMouseMove = (e) => {
let {clientX: x, clientY: y} = e;
this.setState({x, y});
}
render(){
return (
<div onMouseMove={this.handleMouseMove}>
<OldComponent {...this.state} />
</div>
)
}
}
}
@withTracker
class MouseTracker extends Component {
render(){
return (
<div>
<h1>移动鼠标</h1>
<p>{`当期的鼠标位置 x:${this.props.x} y:${this.props.y}`}</p>
</div>
)
}
}
ReactDOM.render(<MouseTracker />, document.getElementById('root'));
shouldComponentUpdate 优化
- 当一个组件的props或state变更,React会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM
- 如果渲染的组件非常多时可以通过覆盖生命周期方法 shouldComponentUpdate 来进行优化
- shouldComponentUpdate 方法会在重新渲染前被触发。其默认实现是返回 true,如果组件不需要更新,可以在shouldComponentUpdate中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作
PureComponent、memo
- 状态不变的话,不会走dom-diff ```jsx import React, { Component } from ‘react’; import ReactDOM from ‘react-dom’;
class Counter extends React.Component { state = {n1: 0, n2: {number: 0}, n3: 0} addNumber1 = () => {this.setState({n1: this.state.n1 + 1});} // 如果是浅比较,属性变了,无法感知,就不会刷新了 // 如果是深比较,属性变了,可以感知,可以刷新,但是性能比较差 // 有没有两全其美的 => immutable.js addNumber2 = () => { let n2 = this.state.n2; n2.number ++; this.setState({n2}); } addNumber3 = () => {this.setState({n3: this.state.n3 + 1});} render(){ console.log(‘Counter render’); return (
class ChildCounter1 extends React.PureComponent { render(){ console.log(‘ChildCounter1 render’); return (
const ChildCounter3 = React.memo(ps => { console.log(‘ChildCounter3 render’); return (
ReactDOM.render(