# react基础

  1. React官网资料 (opens new window)
  2. 由浅入深快速掌握React Fiber (opens new window)
  3. 受控组件和非受控组件 (opens new window)
  4. react 生命周期 & 执行顺序 (opens new window)

# 1、综述

# 1、简史及版本

  • 2011年诞生
  • 2015年发布v0.14.0版本:拆分react package into two,react and react-dom
  • 2016年发布v15.0版本:虚拟dom的diff操作同步执行
  • 2017年发布React v16.0版本:使用fiber架构(分片),<ie11需使用polyfill
    • react v16.2.0(2017年):增加Fragment组件
    • react V16.3.0(2018年):增加主要的api,React.createRef()、React.forwardRef()
    • react V16.6.0(2018年):增加主要的api,React.memo()、React.lazy()
    • react V16.8.0(2019年):增加React Hooks,用来解决状态逻辑复用问题,且不会产生 JSX 嵌套地狱
  • 2020年发布React v17.0版本:引入渐进式升级

# 2、生态

  • 脚手架/框架

    • umi:可插拔的企业级react应用框架
    • create react app:官方支持的创建react单页应用程序的方法
    • Nextjs:ssr框架
    • React-vr:vr框架,适用于展厅、房屋设计
    • Reactxp:多端框架
  • 组件库

    • ant Design系列:pc、mobile,引入Ant Design设计概念
    • Material-UI:实现了谷歌Material Design设计规范,世界流行界面
  • 工具类

    • Redux:遵循函数式编程思想的状态管理插件
    • Mobx:面向对象变迁和响应式编程的状态管理插件
    • Immutable-js:解决javasript Immutable Data的问题
  • 跨端类

    • Remax:阿里的React跨端框架,目前支持支付宝、微信、字节小程序
    • Taro:类React跨端框架,支持主流小程序及React Native
    • React Native:js编写原生的React框架
  • 其他

    • react-window和react-virtualized:虚拟滚动库,提供可服用组件,用于展示列表、网络和表格数据。

# 2、基础

# 1、JSX

  • 合法的jsx元素
    • 普通的dom标签,如div/p/span等
    • 申明的react组件,例如通过class或者函数创建的jsx组件,用户定义的组件必须以大写字母开头
    • false、null、undefined、true为合法元素,不会渲染
    • 字符串(最终会渲染一个text节点)
    • 数字类型,最终会渲染出来
{false && (<p>test</p>)} {/* 渲染空元素 */}
{0 && (<p>true</P>)}   {/* 与门如果出现非布尔值,渲染与预期有出入,渲染出0 */}
{0 ? null : (<p>true</P>)} {/* 建议使用三目运算符 */}
1
2
3
  • 语法
    • {}使用js表达式:三目运算符、执行函数、数据map等
    • class/for这类html属性是关键字,使用className、htmlFor的形式来定义
    • props默认值为true
    • 点语法,一个模块导出多个组件,可以使用点语法直接使用
    • 注释:{/* xxx */}
//点语法
import React from 'react';
const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}
function BlueDatePicker() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <MyComponents.DatePicker color="blue" className="text" {...props}/>;
}

//动态组件
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  const SpecificStory = components[props.storyType];
  return (
    <SpecificStory story={props.story} 
  		onClick={() => console.log("clicked!")}/>
  );
}
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
26
27
28
  • 语法糖
    • 本质为React.createElement(component, props, ...children)函数的语法糖
    • babel-plugin-transform-react-jsx插件,可以将 jsx 编译为 react 的内部⽅法createElement

# 2、函数组件和class组件

在react中,可以使用class形式或是函数的形式来创建一个组件:

//函数组件
function Foo(props) { 
  return (
    <div>{ props.text || 'Foo'}</div>
  ); 
}

//类组件
class Bar extends React.Component {
	render() {
		return (
			<div>{this.props.text || 'Bar'}</div>
 		);
	} 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

两种组件的区别:

  • 加载的 props ⽅式不同,函数式定义组件从组件函数的参数加载;class 形式的组件通过 this.props 获取传⼊的参数

  • 函数式组件⽐较简单,内部⽆法维护状态;class 形式内部可以通过 this.state 和 this.setState ⽅法定义和更新内部state ,同时更新 render ⾥⾯的函数渲染的结果

  • class 组件内部可以定义更多的⽅法在实例上,但是函数式组件⽆法定义

  • class组件需要使用new实例化,函数组件直接使用

# 3、有/无状态组件

# 1、有状态组件

  • 特点

    • 是类组件
    • 有继承
    • 可以使用this
    • 可以使用react的生命周期
    • 使用较多,容易频繁触发生命周期钩子函数,影响性能
    • 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染
  • 使用场景

    • 需要使用到状态的
    • 需要使用状态操作组件的(无状态组件的也可以实现,通过新版本react hooks也可实现)
  • 总结

    类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载)对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件

# 2、无状态组件

  • 特点

    • 不依赖自身的状态state
    • 可以是类组件或者函数组件。
    • 可以完全避免使用 this 关键字(由于使用的是箭头函数事件无需绑定)
    • 有更高的性能,当不需要使用生命周期钩子时,应该首先使用无状态函数组件
    • 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。
  • 使用场景

    • 组件不需要管理 state,纯展示
  • 优点

    • 简化代码、专注于 render
    • 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
    • 视图和数据的解耦分离
  • 缺点

    • 无法使用 ref
    • 无生命周期方法
    • 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染
  • 总结

    组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件,比如自定义的 Button、Input等组件

# 4、受控组件和非受控组件

  • 受控组件:表单数据是由 React 组件来管理的,推荐使用

  • 非受控组件:表单数据将交由 DOM 节点来处理,可以通过ref获取表单中数据

//受控组件
class Form extends Component {
  constructor() {
    super();
    this.state = {
      name: '',
    };
  }

  handleNameChange = (event) => {
    this.setState({ name: event.target.value });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={this.handleNameChange}
        />
      </div>
    );
  }
}

//非受控组件
class Form extends Component {
  handleSubmitClick = () => {
    const name = this._name.value;
    // do something with `name`
  }

  render() {
    return (
      <div>
      	{/* defaultValue为初始值,通过ref可获取input值 */}
        <input type="text" ref={input => this._name = input} defaultValue="Jian"/>
        <button onClick={this.handleSubmitClick}>Sign up</button>
      </div>
    );
  }
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 5、组件生命周期

class组件中,可申明如下生命周期:

# 1、老版本

  • componentWillMount:在新节点插入DOM结构之前触发
    • render:componentWillMount和componentDidMount之间触发
  • componentDidMount:在新节点插入DOM结构之后触发
    • 初始化请求最好在componentDidMount中发送请求,之前是服务器渲染阶段,不适合发送请求;请求中可能有操作dom操作,会出现问题。客户端发送请求api最好放在服务器不会渲染的方法里,在组件挂载至dom元素后再进行请求更新
  • componentWillUnMount:在组件从DOM中移除时立即触发
  • componentWillReceiveProps:已加载的组件收到新的参数时调用,只有props参数更新时才会触发
  • shouldComponentUpdate:组件判断是否重新渲染时调用,返回false取消更新组件,不会调用render
  • componentWillUpdate:当你的组件再次渲染时,在render方法前调用(在组件的props或者state发生改变时会触发该方法)
  • componentDidUpdate:在render函数执行完毕,且更新的组件已被同步到DOM后立即调用,该方法不会在初始化渲染时触发

加载顺序:

  • 首次加载时:componentWillMount -> render -> componentDidMount

  • 参数变化时:componentWillReceiveProps(props参数变化) -> shouldComponentUpdate

    -> componentWillUpdate -> render -> componentDidUpdate

  • 组件卸载:componentWillUnMount

# 2、新版本

新增:

  • static getDerivedStateFromProps(props, state):在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。条件: state 的值在任何时候都取决于 props
  • getSnapshotBeforeUpdate():在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
  • static getDerivedStateFromError(error):会在后代组件抛出错误后被调用 ,它将抛出的错误作为参数,并返回一个值以更新 state
  • componentDidCatch(error, info):在后代组件抛出错误后被调用,用于记录错误之类的情况:

加载顺序:

  • 挂载时:constructor() -> static getDerivedStateFromProps() -> render() -> componentDidMount()

  • 更新时:static getDerivedStateFromProps() -> shouldComponentUpdate()

    -> render() -> getSnapshotBeforeUpdate() -> componentDidUpdate()

  • 卸载时:componentWillUnmount()

  • 错误处理:当渲染过程,生命周期,或子组件的构造函数中抛出错误时 static getDerivedStateFromError() -> componentDidCatch()

# 6、事件代理

react中的事件使用事件代理,采用全局单例的event对象,用于跨端、兼容性和性能提升,异步操作最好将对象内部需要的值进行拷贝,否则会导致this指向问题。

import React, {Component} from 'react';
class App extends Component {
  clickHandle1(e){
    setTimeout(function(){
      console.log('button1 click', e.currentTarget.innerText);
    }, 1000)
  }
  clickHandle2(e){
    console.log('button2 click', e.currentTarget.innerText);
  }
  render(){
    return (
      <div className="App">
        {/* click1报错,click2输出button2 */}
        {/* 解决方法:
        		clickHandle1(e){
        		  let text = e.currentTarget.innerText;
              setTimeout(function(){
                console.log('button1 click', text);
              }, 1000)
            }
         */}
        <button onClick={this.clickHandle1}>button1</button>
        <button onClick={this.clickHandle2}>button2</button>
      </div>
    )
  }
}
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
26
27
28

# 7、immutable和immer

  1. 函数引用:render函数中,使用箭头函数或者bind向子组件传递函数时,每次会重新创建函数,导致子组件进行渲染,影响性能,建议写入constructor里或外部提取使用箭头函数
class App extends React.Component>{
  constructor(props){
    super(props);
    this.state = {
      num: 1,
      title: "Jian"
    }
    //推荐写法1,在constructor中使用bind
    this.handleClick2 = this.handleClick1.bind(this);
  }
  
  handleClick1(){
    this.setState({
      num: this.state.num + 1
    })
  }
	//推荐写法2,使用箭头函数绑定
  handleClick3 = () => {
    this.setState({
      num: this.state.num + 1
    })
  }
	
  render(){
    return (
      <div>
      {/* 不推荐写法 */}
			<ChildComponent onClick={this.handleClick1.bind(this)}></ChildComponent>
			<ChildComponent onClick={()=> this.handleClick1()}></ChildComponent>
					
			{/* 考点1解决方法:提取函数 */}
			<ChildComponent onClick={this.handleClick2}></ChildComponent>
			<ChildComponent onClick={this.handleClick3}></ChildComponent>
      </div>
    )
  }
}
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
26
27
28
29
30
31
32
33
34
35
36
37
  1. 对象引用:传递给子组件对象时,使用深拷贝会引起的对象的引用断裂,等价于每次传入子组件中的对象为新对象,导致子组件重新渲染,可使用immutable库或者immer类库避免,可配合 shouldComponentUpdate 进行追踪,来进⾏性能优化
//immutable库使用
const immutable = require('immutable')
const data = {
  key1: 'test1'key2: 'test2'
}
const a = immutable.fromJS(data);//转换为内置对象,
const b = a.set('key1', 'valueb');
console.log(a.key1 === b.key1);  //false
console.log(a.key2 === b.key2);  //true

//immer库使用
const produce = require('immer')
const state = {
  key1: 'test1'key2: 'test2'
}
const newState = produce(state, (draft) => {
  draft.key1 = 'newKey1';
})
console.log(newState.key1 === state.key1);  //false
console.log(newState.key2 === state.key2);  //true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 8、常用API

# 1、React.creatElement

React.createElement( //创建并返回指定类型的新React元素
  type, 			//标签名字符串,class组件或函数组件,fragment类型
  [props],
  [...children]
)
1
2
3
4
5

# 2、React.Component和React.PureComponent

  • React.Component是使用 ES6 classes方式定义 React 组件的基类,必须有render函数。

  • React.PureComponent 与 React.Component很相似,两者的区别在于React.Component并未实现 shouldComponentUpdate()(,而 React.PureComponent`中以浅层对比 prop 和 state 的方式来实现了该函数。

解析:

PureComponent表示一个纯组件,可以用来优化React程序,减少render函数执行的次数,从而提高组件的性能。

在React中,当prop或者state发生变化时,可以通过在shouldComponentUpdate生命周期函数中执行return false来阻止页面的更新,从而减少不必要的render执行。React.PureComponent会自动执行 shouldComponentUpdate。

不过,pureComponent中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候render是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent一般会用在一些纯展示组件上。

使用pureComponent的好处:当组件更新时,如果组件的props或者state都没有改变,render函数就不会触发。省去虚拟DOM的生成和对比过程,达到提升性能的目的。这是因为react自动做了一层浅比较。

# 3、context

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

在react-router-dom以及redux中常使用,重点掌握内容

  • React.createContext(defaultValue)

    • 创建一个context

    • 当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值

    • 只有当组件所处的树中没有匹配到Provider时,defaultValue才生效

  • Context.Provider

    • 每个context对象都会返回一个Provider react组件,用于传递数据
    • 每个Provider接收value属性,传递给消费组件
    • 可一对多,一个Provider对应多个消费者;可嵌套,里层覆盖外层数据
    • value值变化,消费组件重新渲染,新旧变化使用Object.is
    • provider及其内部consumer组件不受制于shouldComponentUpdate函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新
  • Context.Consumer

    • react组件可以订阅到context变更,用于在函数组件中订阅context
    • 子元素为函数,接收context值,返回React节点,{context => (/* 基于context值渲染 */)}
  • Context.displayName:context对象有一个displayName的属性,类型为字符串,用于devtools显示context的name

  • Class.contextType:挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext()创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  // 使用 static 这个类属性来初始化你的 contextType,Class.contextType的api
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}
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
26
27
28
29
30
31
32
33
34
35

# 4、Ref与Ref转发

  • React.createRef():创建ref,通过ref获取dom元素
  • React.forwardRef((props, ref)=>{/* */}):转发ref到子组件
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
1
2
3
4
5
6
7
8
9

# 5、memo

React.memo为高阶组件,props检查,渲染缓存,浅层比较:如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

注意:React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useStateuseReduceruseContext的 Hook,当 state 或 context 发生变化时,它仍会重新渲染

//1、使用第一个参数浅层比较
const MyComponent = React.memo(function MyComponent(props) {
  /* 使用 props 渲染 */
});

//2、使用第2个参数可进行深入比较
function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Last Updated: 12/25/2021, 6:57:50 PM