zoukankan      html  css  js  c++  java
  • 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数据。

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

  • 相关阅读:
    python分析文本文件/json
    python中文件操作
    python异常处理
    socket网络模块
    层模型--固定定位
    层模型--相对定位
    层模型--绝对定位
    什么是层模型?
    浮动模型
    流动模型/a标签换行问题
  • 原文地址:https://www.cnblogs.com/Ewarm/p/12871588.html
Copyright © 2011-2022 走看看