React学习笔记(四)—— 组件通信与状态管理、Hooks、Redux、Mobe

发布时间 2023-03-23 08:52:12作者: 张果

react管理状态的工具:

1、利用hooks进行状态管理;

2、利用Redux进行状态管理,这种方式的配套工具比较齐全,可以自定义各种中间件;

3、利用Mobx进行状态管理,它通过透明的函数响应式编程使得状态管理变得简单和可扩展。

2013 年 5 月 React 诞生。但 2015 年之前,大概都是 jQuery 的天下。2015 年 3 月 React 0.13.0 发布,带来了 class 组件写法。

在 React class 组件时代,状态就是 this.state,使用 this.setState 更新。

为避免一团乱麻,React 引入了 "组件" 和 "单向数据流" 的理念。有了状态与组件,自然就有了状态在组件间的传递,一般称为 "通信"。

父子通信较简单,而深层级、远距离组件的通信,则依赖于 "状态提升" + props 层层传递。

于是,React 引入了 Context,一个用于解决组件 "跨级" 通信的官方方案。

但 Context 其实相当于 "状态提升",并没有额外的性能优化,且写起来比较啰嗦。

为优化性能,一般会添加多个 Context,写起来就更啰嗦。在项目没那么复杂时,还不如层层传递简单。

Context 没那么好用,React 官方也没什么最佳实践,于是一个个社区库就诞生了。

目前比较常用的状态管理方式有hooks、redux、mobx三种。

一、组件通信

(1).组件的特点

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据

在组件化过程中,通常会将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能

(2).知道组件通讯意义

而在这个过程中,多个组件之间不可避免的要共享某些数据

为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通、这个过程就是组件通讯

1.1、父传子

父组件向子组件通信是通过父组件的props传递数据完成。

UserList.jsx接收父组件的数据,展示用户信息,子组件:

import React, { Component } from 'react'

export default class UserList extends Component {
  render() {
    return (
      <div>
        <ul>
          {this.props.users.map(user =><li key={user.id}>
            {user.name}
          </li>)}
        </ul>
      </div>
    )
  }
}

UserListContainer.jsx向子组件传递数据,父组件:

import React, { Component } from 'react'
import UserList from './UserList'

export default class UserListContainer extends Component {
    state={users:[]}
    componentDidMount(){
        const users=[
            {id:"1001",name:"Jone"},
            {id:"1002",name:"Mali"},
            {id:"1003",name:"Locy"},
            {id:"1004",name:"Rose"},
            {id:"1005",name:"Jack"}
        ]
        this.setState({users:users});
    }
  render() {
    return (
      <div><div>用户信息列表</div>
      <UserList users={this.state.users}/>
      </div>
    )
  }
}

运行结果:

 

 

 解释:.....

1.2、子传父

子传父依然使用props,父组件先给子组件传递一个回调函数,子组件调用父组件的回调函数传入数据,父组件处理数据即可。

在UserList中添加新增加功能:

import React, { Component } from 'react'

export default class UserList extends Component {
  state={newUser:""}
  handleChange=e=>{
    this.setState({newUser:e.target.value});
  }
  handleClick=e=>{
    if(this.state.newUser&&this.state.newUser.length>0){
      this.props.onAddUser(this.state.newUser);
    }
  }
  render() {
    return (
      <div>
        <ul>
          {this.props.users.map(user =><li key={user.id}>
            {user.name}
          </li>)}
        </ul>
        <div>
          姓名:<input type="text" onChange={this.handleChange} value={this.state.newUser}></input>
        <button onClick={this.handleClick} type="submit">新增</button>
        </div>
      </div>
    )
  }
}

在UserListContainer中添加onAddUser参数与函数:

import React, { Component } from 'react'
import UserList from './UserList'

export default class UserListContainer extends Component {
    state={users:[]}
    componentDidMount(){
        const users=[
            {id:"1001",name:"Jone"},
            {id:"1002",name:"Mali"},
            {id:"1003",name:"Locy"},
            {id:"1004",name:"Rose"},
            {id:"1005",name:"Jack"}
        ]
        this.setState({users:users});
    }

    onAddUser(newUser){
        let users=this.state.users;
        this.setState({users:users.concat({
            id:parseInt((users[users.length-1].id)+1)+"",
            name:newUser
        })});
    }
  render() {
    return (
      <div><div>用户信息列表</div>
      <UserList users={this.state.users} onAddUser={this.onAddUser.bind(this)}/>
      </div>
    )
  }
}

运行:

 

 解释:....

1.3、兄弟组件间通信

兄弟组件不能直接相互传送数据,需要通过状态提升的方式实现兄弟组件的通信,即把组件之间需要共享的状态保存到距离它们最近的共同父组件内,任意一个兄弟组件都可以通过父组件传递的回调函数来修改共享状态,父组件中共享状态的变化也会通过props向下传递给所有兄弟组件,从而完成兄弟组件之间的通信。

 

 我们在UserListContainer中新增一个子组件UserDetail,用于显示当前选中用户的详细信息,比如用户的年龄、联系方式、家庭地址等。这时,UserList 和 UserDetail 就成了兄弟组件,UserListContainer是它们的共同父组件。当用户在 UserList中点击一条用户信息时,UserDetail需要同步显示该用户的详细信息,因此,可以把当前选中的用户 currentUser保存到UserListContainer的状态中。

UserList.jsx

import React, { Component } from 'react'
import "./css/userList.css"

/**
 * 用户列表组件
 */
export default class UserList extends Component {
  /**
   * 构造函数
   * @param {*} props 
   */
  constructor(props) {
    super(props);
    this.state = { newUser: ""};
  }

  /**
   * 输入框内容变化事件
   * @param {*} e 
   */
  handleChange = e => {
    this.setState({ newUser: e.target.value });
  }

  /**
   * 新增用户按钮点击事件
   * @param {*} e 
   */
  handleClick = e => {
    if (this.state.newUser && this.state.newUser.length > 0) {
      this.props.onAddUser(this.state.newUser);
    }
  }

  /**
   * 用户列表项点击事件
   * @param {*} userId 
   */
  handleSelect(userId) {
    this.props.onSetCurrentUser(userId);
  }
  /**
   * 渲染函数
   */
  render() {
    return (
      <div>
        <ul>
          {this.props.users.map(user => <li key={user.id} className={user.id === this.props.currentUserId ? "active" : ""} onClick={this.handleSelect.bind(this, user.id)}>
            {user.name}
          </li>)}
        </ul>
        <div>
          姓名:<input type="text" onChange={this.handleChange} value={this.state.newUser}></input>
          <button onClick={this.handleClick} type="submit">新增</button>
        </div>
      </div>
    )
  }
}

UserDetails.jsx

import React, { Component } from 'react'

export default class UserDetails extends Component {
  render() {
    return (
        this.props.currentUser?
        <div>
        <h2>详细信息</h2>
        <fieldset>
            <legend>用户</legend>
            <p>
                {/* 用户编号 */}
                编号:{this.props.currentUser.id}
            </p>
            <p>
                {/* 用户姓名 */}
                姓名:{this.props.currentUser.name}
            </p>
        </fieldset>
      </div>:""   )
  }
}

UserListContainer.jsx

import React, { Component } from 'react'
import UserList from './UserList'
import UserDetails from './UserDetails'

export default class UserListContainer extends Component {
    state={users:[],currentUserId:null}
    componentDidMount(){
        const users=[
            {id:"1001",name:"Jone"},
            {id:"1002",name:"Mali"},
            {id:"1003",name:"Locy"},
            {id:"1004",name:"Rose"},
            {id:"1005",name:"Jack"}
        ]
        this.setState({users:users});
    }

    // 添加用户
    onAddUser(username){
        let users=this.state.users;
        let newUser={
            id:(parseInt(users[users.length-1].id)+1)+"",
            name:username
        };
        this.setState({users:users.concat(newUser),currentUserId:newUser.id});
    }

    // 设置当前用户
    onSetCurrentUser(userId){
        this.setState({currentUserId:userId});
    }

  render() {
    const users=this.state.users.filter(user=>user.id===this.state.currentUserId);
    const currentUser=users&&users.length>0&&users[0];
    return (
      <div><div>用户信息列表</div>
      <UserList users={this.state.users} onAddUser={this.onAddUser.bind(this)} onSetCurrentUser={this.onSetCurrentUser.bind(this)} currentUserId={this.state.currentUserId}/>
      <UserDetails currentUser={currentUser} />
      </div>
    )
  }
}

运行效果:

解释:username

1.4、多级组件通信

当组件所处层级太深时,往往需要经过很层的props传递才能将所需的数据或者回调函数传递给使用组件,所以props作为桥梁通信便会显得很麻烦。React提供了一个context上下文,让任意层级的子组件都可以获取父组件中的状态和方法。

Parent.jsx 父

import React, { Component } from 'react'
import Sub1 from './Sub1';

export default class Parent extends Component {
    state={
        n:100
    }

    setN(n){
        this.setState({n});
    }

  render() {
    return (
     <div style={{backgroundColor:"lightblue"}}>
        <ul>
            <li>
            <div>
            <h2>父组件 n={this.state.n}</h2>
            </div>
            <Sub1 onSetN={this.setN.bind(this)}></Sub1>
            </li>
        </ul>
      </div>
    )
  }
}

Sub1.jsx 子

import React, { Component } from 'react'
import Sub11 from './Sub11';

export default class Sub1 extends Component {

   setNumber() {
        let n=parseInt(this.txtInput.value);
        this.props.onSetN(n);
    }
  render() {
    return (
      <div style={{background:"lightred"}}>
        <ul>
            <li>
            <h2>子组件:Sub1</h2>
            <p>
                <input ref={input=>this.txtInput=input} type="text" />
                <button onClick={this.setNumber.bind(this)}>设置N的值</button>
            </p>
            <Sub11 onSetN={this.props.onSetN}/>
            </li>
        </ul>
      </div>
    )
  }
}

Sub11.jsx 孙

import React, { Component } from 'react'

export default class Sub11 extends Component {

   setNumber() {
        let n=parseInt(this.txtInput.value);
        this.props.onSetN(n);
    }
  render() {
    return (
      <div style={{background:"lightred"}}>
        <ul>
          <li>
          <h2>孙组件:Sub11</h2>
          <p>
              <select ref={input=>this.txtInput=input} type="text">
                <option value={300}>300</option>
                <option value={600}>600</option>
                <option value={900}>900</option>
              </select>
              <button onClick={this.setNumber.bind(this)}>设置N的值</button>
          </p>
          </li>
        </ul>
      </div>
    )
  }
}

结果:

 

 解释:

1.5、Context

当组件所处层级太深时,往往需要经过很层的props传递才能将所需的数据或者回调函数传递给使用组件,所以props作为桥梁通信便会显得很麻烦。React提供了一个context上下文,让任意层级的子组件都可以获取父组件中的状态和方法。

每个组件都拥有context属性,可以查看到:

 

getChildContext:与访问context属性需要通过contextTypes指定可访问的属性一样,getChildContext指定的传递给子组件的属性需要先通过childContextTypes来执行,不然会报错。

使用context改进后的示例如下:

Parent.jsx 父

import React, { Component } from 'react'
import {PropTypes} from 'prop-types'
import Sub1 from './Sub1';

class Parent extends Component {
    state={
        n:100
    }

    setN=(n)=>{
        this.setState({n});
    }
    getChildContext(){
        return {onSetN: this.setN}
    }

  render() {
    return (
     <div style={{backgroundColor:"lightblue"}}>
        <ul>
            <li>
            <div>
            <h2>父组件 n={this.state.n}</h2>
            </div>
            <Sub1></Sub1>
            </li>
        </ul>
      </div>
    )
  }
}

//声明context的属性的类型信息
Parent.childContextTypes = {
    onSetN:PropTypes.func
}

export default Parent;

Sub1.jsx 子

import React, { Component } from 'react'
import Sub11 from './Sub11';
import {PropTypes} from 'prop-types'

class Sub1 extends Component {

   setNumber() {
        let n=parseInt(this.txtInput.value);
        this.context.onSetN(n);
    }
  render() {
    return (
      <div style={{background:"lightred"}}>
        <ul>
            <li>
            <h2>子组件:Sub1</h2>
            <p>
                <input ref={input=>this.txtInput=input} type="text" />
                <button onClick={this.setNumber.bind(this)}>设置N的值</button>
            </p>
            <Sub11/>
            </li>
        </ul>
      </div>
    )
  }
}
//声明要使用的context属性的类型信息
Sub1.contextTypes={
    onSetN: PropTypes.func
}

export default Sub1;

Sub11.jsx 孙

import React, { Component } from 'react'
import {PropTypes} from 'prop-types'

class Sub11 extends Component {

   setNumber() {
        let n=parseInt(this.txtInput.value);
        this.context.onSetN(n);
        console.log(this);
    }
  render() {
    return (
      <div style={{background:"lightred"}}>
        <ul>
          <li>
          <h2>孙组件:Sub11</h2>
          <p>
              <select ref={input=>this.txtInput=input} type="text">
                <option value={300}>300</option>
                <option value={600}>600</option>
                <option value={900}>900</option>
              </select>
              <button onClick={this.setNumber.bind(this)}>设置N的值</button>
          </p>
          </li>
        </ul>
      </div>
    )
  }
}

//声明要使用的context属性的类型信息
Sub11.contextTypes={
  onSetN: PropTypes.func
}

export default Sub11;

结果:

二、Hooks

 

三、Redux

 

四、Mobe