react16.8的新特性及旧特性盘点

首先简单谈谈react和vue的区别:

如果你写过vue,会发现组件的视图指令已编译为修改视图的函数存放在绑定的state里的属性里,所以能够做到靶向修改,而react会以组件为根,重新渲染整个组件子树。所以应避免这些不必要的render。

0、setState和shouldComponentUpdate

setState特性:

1、setState是异步操作函数,很多时候,我们需要想要的state状态更新完成后再进行某些操作。此时,我们可以选择在componentWillUpdate生命周期或者componentDidUpdate生命周期的回调函数去执行我们的操作。虽然也可以达到预期效果,但是这样做不是最佳方法,代码变得破碎,可读性也不好。

因此,此时我们就需要保证setState的同步更新。

有两种方案:

  • 通过回调函数
  • 通过async/await来异步转同步

2、setState会造成不必要的渲染

3、setState并不能很有效的管理所有的组件状态

默认情况下仅对React事件处理程序内的更新进行批处理

如:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    // setState 是不保证同步的所以打印的state依然是旧的,可以在setState中写回调函数得到改变后的state
    console.log(this.state)
    this.setState({ b: true }, ( )=> {
      console.log(this.state)
    });
  }
}
// 不会出现只改变b的情况
  
class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

在类组件中,如果一个li发生变化,那么整个ul都会重新渲染。所以子组件变化,会导致父组件整个发生重新渲染。调用 setState 方法总是会触发 render 方法从而进行 vdom re-render 相关逻辑,哪怕实际上你没有更改到 Component.state。

为了避免这种性能上的浪费,React 提供了一个 shouldComponentUpdate 来控制触发 vdom re-render 逻辑的条件。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。大部分情况下,你应该遵循默认行为。

注:后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次。

1、函数组件:

特点:没有自身的状态无生命周期

//没有自身的状态,相同的props输入必然会获得完全相同的组件展示。不需要关心组件的一些生命周期函数和渲染的钩子更简洁。
import React, { Component } from "react";
const Button = ({ day }) => {
  return (
    <div>
      <button className="btn btn-warning">我是 {day}</button>
    </div>
  );
};
class Greeting extends Component {
  render() {
    return <Button day="纯函数组件"></Button>;
  }
}
export default Greeting;

使用场景:

一些只渲染一次的纯展示的场景,比如一个列表、表格等。

2、纯组件(pureComponent):

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。除非 shouldComponentUpdate() 返回 false,否则 setState() 将始终执行重新渲染操作。首次渲染或使用 forceUpdate() 时不会调用该方法。所以pureComponent就相当于是一个写好shouldComponentUpdate的类组件。

和类组件的区别:自带渲染性能优化(shouldComponentUpdate)

但是:React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()

如果对象包含复杂的数据结构,它可能会因深层的数据不一致而产生错误的否定判断(表现为对象深层的数据已改变视图却没有更新)

解决方法:

注:但是无论是普通组件还是pure纯组件,发生状态变化时候,如果state不是一个普通对象,shouldComponentUpdate就无法拦截复杂数据结构的数据变化,因为地址没有变,比如push数据到一个数组,新的数组和旧的数组指向同一个地址,无法比较是否变化,这时就要借助immutable

**immutable把变化的节点加上原来的老节点,返回一个指向新地址的新树(新对象)。类似深拷贝。**如之前:{a: 1, b: 2},之后:{a: 1, b: 3},这样shouldComponentUpdate就可以继续比较这两个对象了。

//demo1: PureComponent的自动为我们添加的shouldComponentUpate函数
import React, { PureComponent } from "react";
class CounterButton extends PureComponent {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  render() {
    return (
      <button
        className="btn btn-info"
        onClick={() => this.setState(state => ({ count: state.count + 1 }))}
      >
        Count: {this.state.count}
      </button>
    );
  }
}
export default CounterButton;

// pure组件只是对类组件的升级,没有解决掉复杂对象无法比较的问题,还是要引入immutable。

// demo2
import React from "react";
const { List } = require("immutable");
// let data = [];
//创建不可变的对象
let data = List(["start"]);
class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: data
    };
  }
  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => {
          //   data.push(Math.random());
          this.setState(state => ({ count: this.state.count.push(Math.random()) }));
        }}
      >
        {/* Count: {this.state.count.length} */}
        Count: {this.state.count.size}
      </button>
    );
  }
}
export default CounterButton;

应用场景:

如果组件的内部状态改变的很频繁,并且不想染外部一起渲染,就使用pureComponent,它内部会自动计算并拦截状态的变化,不用自己去维护shouldComponentUpdate函数了。

3、高阶组件

用于对普通组件进行包装,并扩展功能。

高阶函数:
// demo1
// hoc为高阶函数
function hello (){
    console.log("?我是高阶组件")
}
function hoc(fn){
  	// 关键是要返回一个函数,不能执行
    return ()=>{
      console.log("first");
      fn();
      console.log("end");
    }
}
const hocresult = hoc(hello);
hocresult();

// demo2
function welcome(username) {
    console.log('welcome ' + username);
}

function goodbey(username) {
    console.log('goodbey ' + username);
}
//高阶函数
function wrapWithUsername(wrappedFunc) {
    let newFunc = () => {
        let username = localStorage.getItem('username');
        wrappedFunc(username);
    };
    return newFunc;
}

// eslint-disable-next-line no-func-assign	一般不能重新定义函数
welcome = wrapWithUsername(welcome);
// eslint-disable-next-line no-func-assign
goodbey = wrapWithUsername(goodbey);

welcome();
goodbey();
高阶组件:

用函数包裹,函数参数接受一个普通组件,并最终返回一个新组件,这个返回的新组件就叫做高阶组件

//=========高阶组件的实战代码demo1=====
import {Component} from 'react'
function HOCFactoryFactory(...params){
    return function HOCFactory(WrappedComponent){
        return class HOC extends Component{
            render(){
                return <WrappedComponent {...this.props} />
            }
        }
    }
}
//使用方式1,注入
@HOCFactoryFactory({})
class WrappedComponent extends React.Component{}
//使用方式2
HOCFactoryFactory({})(WrappedComponent)

//=========高阶组件的实战代码demo2=====
//##高阶组件之后的代码
//注值 localStorage.username = "老袁"
const wrapWithUsername = WrappedComponent => {
  class NewComponent extends Component {
    constructor() {
      super();
      this.state = {
        username: ""
      };
    }
    componentWillMount() {
      let username = localStorage.getItem("username");
      this.setState({
        username: username
      });
    }

    render() 
    	// 这里重新包装了传入的普通组件,并交给新生成的高阶组件去渲染
      return <WrappedComponent username={this.state.username} />;
    }
  }

  return NewComponent;
};

class Welcome extends Component {
  render() {
    return <div className="text-warning">welcome {this.props.username}</div>;
  }
}
// 把Welcome升级成高阶组件
Welcome = wrapWithUsername(Welcome);

class Goodbye extends Component {
  render() {
    return <div className="text-info">goodbye {this.props.username}</div>;
  }
}
//升级高阶组件
Goodbye = wrapWithUsername(Goodbye);
class Greeting extends Component {
  render() {
    return (
      <>
        <Welcome /> <Goodbye />
      </>
    );
  }
}
export default Greeting;

应用场景:redux

4、插槽(Portals组件)

Portals 提供了一个顶级的方法,使得我们有能力把一个子组件渲染到父组件 DOM 层级以外的 DOM 节点上。

使用:ReactDOM.createPortal()方法

import React from 'react'
import ReactDOM from 'react-dom'
import "./component.css"
//组件插槽
const portalElm = document.createElement('div');
portalElm.className="txtcenter"
document.body.appendChild(portalElm)

class App extends React.Component {
  state = {
    show: true,
  }

  handleClick = () => {
    this.setState({
      show: !this.state.show,
    })
  }

  render() {
    return (
      <div>
        <button className="btn btn-primary" onClick={this.handleClick}>动态展现Portal组件</button>
        {this.state.show ? (
          <div>{ReactDOM.createPortal(<span>Portal组件</span>, portalElm)}</div>
        ) : null}
      </div>
    )
  }
}
export default App

应用场景:弹窗

5、处理异步数据或组件(suspense)

react支持用suspense去处理异步数据,不需要async/await。

//新增了render 新的返回类型:fragments 和 strings
import React, { Suspense, lazy } from "react";
import "./suspense.css";
// import { useFetch } from "react-hooks-fetch";
// console.log("异步加载数据", useFetch);
//动态加载组件
const LazyComp = lazy(() => import("./lazy"));

function fetchApi() {
  const promise = new Promise(resolve => {
    setTimeout(() => {
      resolve("Data resolved");
    }, 3000);
  });
  return promise;
}
//创建Fetcher
var cached = {};
const createFetcher = promiseTask => {
  let ref = cached;
  return () => {
    const task = promiseTask();
    task.then(res => {
      ref = res;
    });
    console.log("?--ref",ref);
    console.log("?--cached",cached);
    if (ref === cached) {
      throw task;
    }
    //得到结果输出
    console.log("?",ref);
    return ref;
  };
};
const requestData = createFetcher(fetchApi);
function SuspenseComp() {
    // const {error,data} = useFetch("a.php");
    // console.log("数据?",data)
    // if (error) return <span>出错了/(ㄒoㄒ)/~~</span>;
    // if (!data) return null;
    // return <span>RemoteData:{data.title}</span>;
  const data = requestData();
  return <p className="text-warning">{data}</p>;
}

export default () => (
  <Suspense fallback={<div className="text-danger">loading<i></i></div>}>
    <SuspenseComp />
    <LazyComp />
  </Suspense>
);

6、memo组件

React.memo() 是高阶函数能将函数组件转换成类似于React.PureComponent组件。

//React.memo() 是高阶函数能将函数组件转换成类似于React.PureComponent组件
import React, { memo, Component } from "react";

function Child({ seconds }) {
  console.log("I am rendering");
  return <div>Memo组件 seconds->{seconds} </div>;
}

function areEqual(prevProps, nextProps) {
  if (prevProps.seconds === nextProps.seconds) {
    return true;
  } else {
    return false;
  }
}
// const RocketComponent = props => <div>my rocket component. {props.fuel}!</div>;

// 创建一个只在prop改变时发生渲染的版本
// const MemoizedRocketComponent = memo(RocketComponent);
// const memocom = () => {
//   return memo(Child, areEqual);
// };
const DemoComponent = memo(Child, areEqual);

class Greeting extends Component {
  render() {
    return <DemoComponent seconds="20" />;
  }
}
export default Greeting;

// function Child({seconds}){
//     console.log('I am rendering');
//     return (
//         <div>I am update every {seconds} seconds</div>
//     )
// };
// export default React.memo(Child)

7、context API

Context 主要是解决props向多层嵌套的子组件传递的问题,原理是定义了一个全局对象。

子节点要用Consumer包裹

//Context 主要是解决props向多层嵌套的子组件传递的问题,原理是定义了一个全局对象
import React from "react";
import PropTypes from "prop-types";

const { Provider, Consumer } = React.createContext("default");

class Parent extends React.Component {
  state = {
    yideng: "普通字符串?",
    newContext: "京程一灯"
  };

  //   getChildContext() {
  //     return { value: this.state.newContext, yideng: this.state.yideng };
  //   }
  render() {
    //    <React.Fragment> ==  <>
    return (
      <>
        <div>
          <label className="text-warning">父节点=> newContext:</label>
          <input
            type="text"
            value={this.state.newContext}
            onChange={e => this.setState({ newContext: e.target.value })}
          />
        </div>
        <div>
          <label className="text-info">父节点=>yideng:</label>
          <input
            type="text"
            value={this.state.yideng}
            onChange={e => this.setState({ yideng: e.target.value })}
          />
        </div>
        {/* {this.props.children} */}
        <Provider
          value={{ newContext: this.state.newContext, yideng: "普通字符串?" }}
        >
          {this.props.children}
        </Provider>
      </>
    );
  }
}

function Child(props, context) {
  return (
    <Consumer>
      {value => (
        <p className="text-warning">子节点=> newContext: {value.newContext}</p>
      )}
    </Consumer>
  );
}

class Child2 extends React.Component {
  static contextTypes = {
    yideng: PropTypes.string
  };
  render() {
    // return <p>字符串a: {this.context.yideng}</p>;
    return (
      <Consumer>
        {value => <p className="text-info">子节点=> yideng: {value.yideng}</p>}
      </Consumer>
    );
  }
}
Child.contextTypes = {
  value: PropTypes.string
};
// Parent.childContextTypes = {
//   value: PropTypes.string,
//   yideng: PropTypes.string
// };

export default () => (
  <Parent>
    <Child />
    <Child2 />
  </Parent>
);

8、ref

react采用了新的ref方式,使用React.createRef()

forwardRef,省去了ref复杂程度。

// demo1
import React from 'react'

const TargetComponent = React.forwardRef((props, ref) => (
  <input type="text" ref={ref} />
))

export default class Comp extends React.Component {
  constructor() {
    super()
    this.ref = React.createRef()
  }

  componentDidMount() {
    this.ref.current.value = '转发ref成功?'
  }

  render() {
    return <TargetComponent ref={this.ref} />
  }
}
  
// demo2	
import React from 'react'

export default class RefDemo extends React.Component {
  constructor() {
    super()
    this.objRef = React.createRef();
  }

  componentDidMount() {
    setTimeout(() => {
      this.refs.stringRef.textContent = 'string ref got'
      this.methodRef.textContent = 'method ref got'
      this.objRef.current.textContent = 'obj ref got'
    }, 30)
  }

  render() {
    return (
      <>
        <p  className="text-success" ref="stringRef">span1</p>
        <p ref={ele => (this.methodRef = ele)}>span3</p>
        <p ref={this.objRef}>span3</p>
      </>
    )
  }
}

8、error组件

增加了componentDidCatch生命周期,父组件捕捉错误

import React, { Component } from "react";

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  // 捕捉错误和错误上报程序库一起使用
  componentDidCatch(err, info) {
    this.setState({ hasError: true });
  }
  render() {
    if (this.state.hasError) {
      return <div>Something went wrong!</div>;
    }
    return this.props.children;
  }
}
class Profile extends Component {
    constructor(props) {
      super(props);
      this.state = {  };
    }
    render() {
      return <span>用户名:{this.state.user.push(1)}</span>
    }
  }

class Greeting extends Component {
  render() {
    return (
      <ErrorBoundary>
        <Profile/>
      </ErrorBoundary>
    );
  }
}
export default Greeting;

9、生命周期

9.1 shouldComponentUpdate若返回false不会重新渲染渲染

9.2 可以使用this.setState()的生命周期:

  • componentWillmount()
  • componentWillReceiveProps()
  • componentDidMount()
  • componentDidUpdate()

9.3 react16废弃的生命周期:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

新增生命周期:

  • getSnapshotBeforeUpdate
  • getDerivedStateFromProps代替componentWillReceiveProps

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

用途:因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdatecomponentDidUpdate)之间可能存在延迟。

10、hooks

hooks的诞生是为了解决react开发中遇到的问题

1、this的指向问题。

2、生命周期。

3、原本的函数组件是空的,给函数组件扩展功能。

/**
 * 1.只能在函数组件中使用hooks
 * 2.函数组件业务变更无需修改成class组件
 * 3.告别了繁杂的this和难以记忆的生命周期
 * 4.合并的生命周期componentDidMount、componentDidUpdate、和 componentWillUnmount
 * 5.包装自己的hooks 是基于纯命令式的api
 * 6.更好的完成状态之间的共享 解决原来class组件内部封装问题。也解决了高阶组件和函数组件的嵌套过深
 * 7.useReducer集成redux
 * 8.useEffect接受脏操作等到react更新了DOM之后,它再依次执行我们定义的副作用函数。这里就是一个io且是异步的
 */

/**
 * 以上我们学习过的方法均提供了ref
 * useState 返回有状态值,以及更新这个状态值的函数
 * useEffect 接受包含命令式,可能有副作用代码的函数。
 * useContext 接受上下文对象(从React.createContext返回的值)并返回当前上下文值,
 * useReducer useState的替代方案。接受类型为(state,action) => newState的reducer,并返回与dispatch方法配对的当前状态。 
 * useCallback  返回一个回忆的memoized版本,该版本仅在其中一个输入发生更改时才会更改。纯函数的输入输出确定性
 * useMemo 纯的一个记忆函数
 * useRef 返回一个可变的ref对象,其.current属性被初始化为传递的参数
 * useImperativeMethods 自定义使用ref时公开给父组件的实例值
 * useMutationEffect 更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发
 * useLayoutEffect DOM改变后同步触发。使用它来从DOM读取布局并同步重新渲染
 */
import React, { useState, useEffect } from "react";

const useCount = (initialCount = 0) => {
  const [count, setCount] = useState(initialCount);
  return [count, () => setCount(count + 1), () => setCount(count - 1)];
};

export default () => {
  const [count, increment, decrement] = useCount(1);
  //首次渲染完成
  //   componentDidMount() {
  //     document.title = `You clicked ${this.state.count} times`;
  //   }
  //更新渲染完成
  //   componentDidUpdate() {
  //     document.title = `You clicked ${this.state.count} times`;
  //   }
  //组件卸载阶段 == return function useEffect每次组件变更均执行
  // componentWillUnmount(){

  // }
  useEffect(() => {
    console.log("component update");
    document.title = `标题-${count} times`;
    return () => {
      console.log("unbind");
    };
  }, [count]);

  return (
    <>
      <input type="button" value="增加count" onClick={increment} />
      <span>当前count: {count}</span>
      <input type="button" value="减少count" onClick={decrement} />
    </>
  );
};

11、受控组件和非受控组件

在大多数情况下,我们推荐使用 受控组件 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。

受控组件:

在 HTML 中,表单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。输入的表单数据保存在组件的state属性中,并且只能通过使用 setState()来更新。使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。

class NameForm 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) {
    alert('提交的名字: ' + 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节点处理,借助ref读取dom数据。

两者的选取:

若需要表单即时验证,选择受控组件,不需要即时验证,提交时验证,则可以选择非受控组件。

Logo

前往低代码交流专区

更多推荐