React复习01 - 组件通信与生命周期

252 阅读5分钟

组件的通信

关于setState的细节

setState(stateChange[, callback])

关于setState要知道两点

  • 应该将setState视为请求而不是更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。所以setState是异步的。
  • setState中传入对象,会进行一个浅合并,相当于Object.assign

观察之后证明了两点:
1、 this.setState是异步的

  • 按下同步增加的按钮,可以看到打印出来的state是之前的state,然而视图已经更新
  • 按下异步增加的按钮,可以看到打印出来的state与视图的state相同 2、 this.setState对对象进行了浅合并
  • 按下增加的按钮,只修改count,可以看到打印出来的state中,msg的属性均未变化
  • 按下错误修改的按钮,只对msg.id更新,发现msg.title属性不见了
  • 按下正确修改的按钮,将msg整体通过展开操作符将全部属性拷贝过来,再对想要修改的属性进行覆盖,发现msg.title没有消失
import React, { Component } from "react";

class App extends Component {
  state = {
    count: 0,
    msg: {
      title: "no-change",
      id: 0
    }
  };

  asyncHandleAddCount = async() => {
    await this.setState({ count: this.state.count + 1 });
    console.log(this.state)
  };
  handleAddCount = () => {
     this.setState({ count: this.state.count + 1 });
    console.log(this.state)
  };
  asyncErrorHandleChange = async() => {
    const {msg} = this.state;
    await this.setState({ msg: {id:msg.id+1}});
    console.log(this.state)
  };
  asyncTrueHandleChange = async() => {
    const {msg} = this.state;
    await this.setState({ msg: {...msg,id:msg.id+1}});
    console.log(this.state)
  };

  render() {
    const {count, msg} = this.state;
    return (
      <>
        <div>
          {count} 
          <button onClick={this.asyncHandleAddCount}>异步+</button>
          <button onClick={this.handleAddCount}>同步+</button>
        </div>
        
        <div>
          {msg.title}-----{msg.id} 
          <button onClick={this.asyncErrorHandleChange}>错误更改</button>
          <button onClick={this.asyncTrueHandleChange}>正确修改</button>
        </div>
      </>
    );
  }
}



export default App;

父组件向子组件之间的通信

  • 父组件向子组件的通信通过props进行一个通讯

子组件向父组件之间的通信

  • 子组件向父组件通信通过回调函数
import React, { Component } from "react";


class App extends Component {
  state = {
    count: 0,
  };

  handleAddCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <>
        <Counter count={this.state.count} onAddCount={this.handleAddCount} />
      </>
    );
  }
}

//	这里的函数组件中,默认的参数为props:{}
//	在这里我进行了解构,拿到我想要的属性
function Counter({count, onAddCount}) {
  return (
    <>
      <div>{count}</div>
      //  子组件通过回调函数,向父组件通信
      <button onClick={onAddCount}>+</button>
    </>
  )
}


export default App;

不同组件之间的通信

  • 不同组件之间的通信通过Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

如何创建context

import React, { Component } from "react";
const MyContext = React.createContext();

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

嵌套组件如何使用并更新Context中的值

  • 首先我们创建好的Context对象MyContext具有两个属性PoviderConsumer
  • Provider 接收一个value 属性,传递给消费组件。在Provider下的嵌套组件可通过Consumer访问value
  • Consumer需要函数作为子元素这种做法。这个函数接收当前的 context 值,返回一个React 节点Parent
class App extends Component {
  state = {
    count: 0,
  };

// 实现一个用context做的计数器
  handleAddCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    console.log(MyContext);
    return (
      <>
      // 提供value给后续的嵌套组件
        <MyContext.Provider
          value={{ count: this.state.count, add: this.handleAddCount }}
        >
          <Son />
        </MyContext.Provider>
      </>
    );
  }
}

Son

function Son() {
  return (
    <>
    // 中间层并没有实际意义
      I m Son <br />
      <FunctionGrandson />
    </>
  );
}

FunctionGrandson

function FunctionGrandson() {
  return (
    <>
      <MyContext.Consumer> 
      
      // consumer需要用函数的形式返回一个子节点
        {(context) => {
          console.log(context);
          return (
            <>
              <div>I m FunctionGrandson</div>
              {context.count}
              <button onClick={context.add}>change</button>
            </>
          );
        }}
      </MyContext.Consumer>
    </>
  );
}

Result:

  • 另外,除了使用Consumer,使用类组件形式的组件还可以通过设置contextType,然后通过context属性来访问。

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

class ClassGrandson extends Component {
  static contextType = MyContext;
  render() {
    console.log(this);
    return (
      <>
        <div>I m ClassGrandson</div>
        {this.context.count}
        <button onClick={this.context.add}>+</button>
      </>
    );
  }
}

组件的生命周期

分为三个阶段,挂载,更新以及卸载

挂载mount,从初始化组件 -> 组件构建的视图 -> 渲染到真实dom中

  • constructor(){} --实例化组件调用
  • static getDerivedStateFromProps(props){} --将props关联到state
  • render(){} --构建视图
  • componentDidMount(){} --已经渲染到真实dom

import React, { Component } from "react";


class App extends Component {
//	1==实例化
constructor() {
  super()
  this.state = {
    count: 0
  }
  console.log("constructor-app")
}

//	7==渲染到真实dom
componentDidMount(){
  console.log("componentDidMount-app")
}
//  2==构建虚拟dom
  render() {
    console.log("render-app")
    return (
      <>
        <Counter count={this.state.count}/>
      </>
    );
  }
}

class Counter  extends Component {
//	3==
  constructor(){
    super()
    this.state = { 
      title: "Counter"
     }
     console.log("constructor-counter")
  }

//	6==
  componentDidMount(){
    console.log("componentDidMount-counter")
  }

//	4==
   static getDerivedStateFromProps(props){
     console.log("getDerivedStateFromProps-counter")
     return props;
   }
   
//	5==
  render() { 
    const {count} = this.props;
    console.log("render-counter")
    console.log(this.state)
    return ( 
      <>
        <div>{count}</div>
      </>
     );

  }
}
 
export default App;


更新update, 从组件开始更新 -> 更新结束

  • static getDerivedStateFromProps(props,state){}
  • shouldComponentUpdate(nextProps, nextState){} -- 判断是否更新
  • render(){}
  • getSnapshotBeforeUpdate(){}
  • componentDidUpdate(){} -- 处理副作用(请求)
import React, { Component } from "react";

class App extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }

  handleAddCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <>
        <Counter count={this.state.count} onAddCount={this.handleAddCount} />
      </>
    );
  }
}

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      title: "Counter",
    };
  }
  //  1==
  static getDerivedStateFromProps(props) {
    console.log("getDerivedStateFromProps-counter");
    return props;
  }
  //  2==
  shouldComponentUpdate(nextProps, nextState) {
    console.log(nextProps, nextState);
    //  必须要有返回值,true为继续更新,false打断更新
    return true;
  }
    //  5==
  componentDidUpdate(prevProps, prevState, prevDOM) {
    console.log(prevDOM);
  }
  //  4==
  getSnapshotBeforeUpdate(prevProps, prevState) {
    //  要用这个函数必须要有componentDidUpdate
    //  获取准备更新前的真实DOM快照, 用于获取更新前的DOM快照
    //  DOM快照返回给componentDidUpdate

    return document.querySelector("#count");
  }
  //  3==
  render() {
    const { count } = this.props;
    console.log("render-counter");
    console.log(this.state);
    return (
      <>
        <div id="count">{count}</div>
        <button onClick={this.props.onAddCount}>+</button>
      </>
    );
  }
}

export default App;


卸载阶段

  • componentWillUnmount -> 组件卸载之前清除掉副作用(定时器,事件监听等) 举个例子,我们在组件挂载完毕后给window加上一个resize的事件监听,如果我们把组件卸载之后会发生什么?
import React, { Component } from "react";

class App extends Component {
  constructor() {
    super();
    this.state = {
      show: true,
    };
  }

  handleToggleShow = () => {
    this.setState({ show: !this.state.show });
  };

  render() {
    return (
      <>
        {this.state.show && <Counter />}
        <button onClick={this.handleToggleShow}>toggle</button>
      </>
    );
  }
}

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      height: window.innerHeight,
    };
  }

  showWindowWidth = () => {
    this.setState({ height: window.innerHeight });
  };
  
  // 给window加上监听函数
  componentDidMount() {
    window.onresize = this.showWindowWidth;
  }

  render() {
    return <>{this.state.height}</>;
  }
}

export default App;

组件卸载之后,组件确实销毁了。可是onresize中的处理函数还存着this.setState没被处理。所以我们需要加上componentWillUnmount

class Counter extends Component {
  constructor() {
    super();
    this.state = {
      height: window.innerHeight,
    };
  }

  showWindowWidth = () => {
    this.setState({ height: window.innerHeight });
  };

  componentDidMount() {
    window.onresize = this.showWindowWidth;
  }

//	卸载前将监听器去除
  componentWillUnmount() {
    window.onresize = null;
  }

  render() {
    return <>{this.state.height}</>;
  }
}