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.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” 阶段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之间可能存在延迟。

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数据。

两者的选取:

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