React學習筆記(四)—— 組件通信與狀態管理、Hooks、Redux、Mobe

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>
    )
  }
}

運行結果:

 解釋:數據users在父組件中通過屬性傳遞給子組件UserList,在UserList中通過props接收父組件傳入的數據,完成父傳子,這是最簡單,最基本的一個狀態的傳遞方法,推薦常用。

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>
    )
  }
}

運行:

 

 解釋:在子組件中用戶輸入了一個新的姓名,調用props.addUser方法將新添加的用戶信息發送給父組件完成添加功能,所以這裏實現了子傳父功能。

 * UserListContainer中包含UserList組件,所以UserListContainer是父組件,而UserList是子組件
 * 子組件通過調用父組件中的onAddUser方法將輸入的用戶添加到集合中,完成子傳父功能

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>
    )
  }
}

運行效果:

解釋:在子組件UserList中添加一個username,通過onAddUser將username傳入父組件UserListContainer中,這裏完成了狀態提升,在UserListContainer中再將新添加的用戶傳入給UserDetail組件,實現從父傳給子組件,整個過程實現了兄弟之間的數據傳遞功能。

UserListPro.jsx

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

export default class UserListPro extends Component {
    state={username:""}
    usernameChange=(e)=>{
        this.setState({username:e.target.value})
    }
    handleSubmit=e=>{
        const {username} = this.state;
        if(username&&username.length>0){
            //調用父組件的onAddUser方法將值username傳遞給父組件
            this.props.onAddUser(this.state.username);
        }
        e.preventDefault();
    };

    selectHandle=(e)=>{
        //將當前選擇的用戶編號傳給父組件
        this.props.onSetCurrentId(e.target.id);
    };

  render() {
    console.log(this);
    return (
      <div>
        <ul className='userDetail'>
        {this.props.users.map(user=><li 
        key={user.id} 
        id={user.id} 
        onClick={this.selectHandle}
        className={user.id===this.props.currentId?"active":""}
        >{user.id} - {user.name}</li>)}
        </ul>
        <div>
            <form onSubmit={this.handleSubmit}>
                <p>
                <label>姓名:</label>
                <input type="text" value={this.state.username} onChange={this.usernameChange}/>
                <button>添加</button>
                </p>
            </form>
        </div>
      </div>
    )
  }
}

UserListContainer.jsx

import React, { Component } from 'react'

import UserListPro from './UserListPro';
import UserDetail from './UserDetail';

/**
 * UserListContainer中包含UserList組件,所以UserListContainer是父組件,而UserList是子組件
 * 子組件通過調用父組件中的onAddUser方法將輸入的用戶添加到集合中,完成子傳父功能
 */
export default class UserListContainer extends Component {
    //currentId用於記錄當前用戶的編號
    state={users:[],currentId: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 id=parseInt(this.state.users[this.state.users.length-1].id)+1;
        let id=this.state.users[this.state.users.length-1].id*1+1;
        //將添加的新用戶
        const user={id,name:username};
        //將新用戶添加到users狀態中
        this.setState({users:this.state.users.concat(user),currentId:id});
    }

    //子組件通過該方法設置當前用戶的編號
    onSetCurrentId=(id)=>{
        this.setState({currentId:id});
    }

  render() {
    //根據用戶編號從用戶集合中獲取用戶集合
    const users=this.state.users.filter(user => user.id === this.state.currentId);
    //當前用戶
    let currentUser=null;
    //如果查找了
    if(users&&users.length>0){
        //設置當前用戶
        currentUser=users[0];
    }
    return (
      <div>
        <h2>用戶列表</h2>
        <UserListPro 
        users={this.state.users} 
        onAddUser={this.onAddUser.bind(this)}
        currentId={this.state.currentId}
        onSetCurrentId={this.onSetCurrentId}
        />
        <div>
            <UserDetail currentUser={currentUser}></UserDetail>
        </div>
      </div>
    )
  }
}

UserDetail.jsx

import React, { Component } from 'react'

export default class UserDetail 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>
      :""
    )
  }
}

css/userListPro.css

.userDetail li{
    cursor: pointer;
}
.userDetail li:hover{
    background: lightyellow;
}
.active{
    background: lightyellow;
}

運行結果:

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章