首頁 > 軟體

React Context與setState詳解使用方法

2022-11-28 22:00:14

React中的插槽(slot)

React對於需要插槽的情況非常靈活,有兩種方案可以實現:

元件的children子元素;

props屬性傳遞React元素

children實現插槽

每個元件都可以獲取到 props.children:它包含元件的開始標籤和結束標籤之間的內容。

App.jsx

import React, { Component } from 'react'
import NavBar from './nav-bar'
import NavBarTwo from './nav-bar-two'
export class App extends Component {
  render() {
    const btn = <button>按鈕2</button>
    return (
      <div>
        {/* 1.使用children實現插槽 */}
        <NavBar>
          <button>按鈕</button>
          <h2>哈哈哈</h2>
          <i>斜體文字</i>
        </NavBar>
      </div>
    )
  }
}
export default App

NavBar.jsx

import React, { Component } from 'react'
// import PropTypes from "prop-types"
import "./style.css"
export class NavBar extends Component {
  render() {
    const { children } = this.props
    console.log(children)
    return (
      <div className='nav-bar'>
        <div className="left">{children[0]}</div>
        <div className="center">{children[1]}</div>
        <div className="right">{children[2]}</div>
      </div>
    )
  }
}
// NavBar.propTypes = {
//   children: PropTypes.array
// }
export default NavBar

props實現插槽

app.jsx

import React, { Component } from 'react'
import NavBar from './nav-bar'
import NavBarTwo from './nav-bar-two'
export class App extends Component {
  render() {
    const btn = <button>按鈕2</button>
    return (
      <div>
        {/* 2.使用props實現插槽 */}
        <NavBarTwo 
          leftSlot={btn}
          centerSlot={<h2>呵呵呵</h2>}
          rightSlot={<i>斜體2</i>}
        />
      </div>
    )
  }
}
export default App

NavBarTwo.jsx

import React, { Component } from 'react'
export class NavBarTwo extends Component {
  render() {
    const { leftSlot, centerSlot, rightSlot } = this.props
    return (
      <div className='nav-bar'>
        <div className="left">{leftSlot}</div>
        <div className="center">{centerSlot}</div>
        <div className="right">{rightSlot}</div>
      </div>
    )
  }
}
export default NavBarTwo

Context應用場景

非父子元件資料的共用:

在開發中,比較常見的資料傳遞方式是通過props屬性自上而下(由父到子)進行傳遞。

但是對於有一些場景:比如一些資料需要在多個元件中進行共用(地區偏好、UI主題、使用者登入狀態、使用者資訊等)。

如果我們在頂層的App中定義這些資訊,之後一層層傳遞下去,那麼對於一些中間層不需要資料的元件來說,是一種冗餘的操作。

但是,如果層級更多的話,一層層傳遞是非常麻煩,並且程式碼是非常冗餘的:

React提供了一個API:Context;

Context 提供了一種在元件之間共用此類值的方式,而不必顯式地通過元件樹的逐層傳遞 props;

Context 設計目的是為了共用那些對於一個元件樹而言是“全域性”的資料,例如當前認證的使用者、主題或首選語言;

Context相關API

React.createContext

建立一個需要共用的Context物件:

如果一個元件訂閱了Context,那麼這個元件會從離自身最近的那個匹配的 Provider 中讀取到當前的context值;

defaultValue是元件在頂層查詢過程中沒有找到對應的Provider,那麼就使用預設值

theme-context.js

import React from "react"
// 1.建立一個Context
const ThemeContext = React.createContext({ color: "blue", size: 10 })
export default ThemeContext

user-context.js

import React from "react"
// 1.建立一個Context
const UserContext = React.createContext()
export default UserContext

Context.Provider

每個 Context 物件都會返回一個 Provider React 元件,它允許消費元件訂閱 context 的變化:

Provider 接收一個 value 屬性,傳遞給消費元件;

一個 Provider 可以和多個消費元件有對應關係;

多個 Provider 也可以巢狀使用,裡層的會覆蓋外層的資料;

當 Provider 的 value 值發生變化時,它內部的所有消費元件都會重新渲染;

import React, { Component } from 'react'
import Home from './Home'
import ThemeContext from "./context/theme-context"
import UserContext from './context/user-context'
import Profile from './Profile'
export class App extends Component {
  constructor() {
    super()
    this.state = {
      info: { name: "kobe", age: 30 }
    }
  }
  render() {
    const { info } = this.state
    return (
      <div>
        <h2>App</h2>
        {/* 1.給Home傳遞資料 */}
        {/* <Home name="why" age={18}/>
        <Home name={info.name} age={info.age}/>
        <Home {...info}/> */}
        {/* 2.普通的Home */}
        {/* 第二步操作: 通過ThemeContext中Provider中value屬性為後代提供資料 */}
        <UserContext.Provider value={{nickname: "kobe", age: 30}}>
          <ThemeContext.Provider value={{color: "red", size: "30"}}>
            <Home {...info}/>
          </ThemeContext.Provider>
        </UserContext.Provider>
        <Profile/>
      </div>
    )
  }
}
export default App

Class.contextType

掛載在 class 上的 contextType 屬性會被重賦值為一個由 React.createContext() 建立的 Context 物件:

這能讓你使用 this.context 來消費最近 Context 上的那個值;

你可以在任何生命週期中存取到它,包括 render 函數中;

import React, { Component } from 'react'
import ThemeContext from './context/theme-context'
import UserContext from './context/user-context'
export class HomeInfo extends Component {
  render() {
    // 4.第四步操作: 獲取資料, 並且使用資料
    console.log(this.context)
    return (
      <div>
        <h2>HomeInfo: {this.context.color}</h2>
        <UserContext.Consumer>
          {
            value => {
              return <h2>Info User: {value.nickname}</h2>
            }
          }
        </UserContext.Consumer>
      </div>
    )
  }
}
// 3.第三步操作: 設定元件的contextType為某一個Context
HomeInfo.contextType = ThemeContext
export default HomeInfo

Context.Consumer

這裡,React 元件也可以訂閱到 context 變更。這能讓你在 函數式元件 中完成訂閱 context。

這裡需要 函數作為子元素(function as child)這種做法;

這個函數接收當前的 context 值,返回一個 React 節點;

什麼時候使用Context.Consumer呢?

1.當使用value的元件是一個函數式元件時;

2.當元件中需要使用多個Context時;

import ThemeContext from "./context/theme-context"
function HomeBanner() {
  return <div>
    {/* 函數式元件中使用Context共用的資料 */}
    <ThemeContext.Consumer>
      {
        value => {
          return <h2> Banner theme:{value.color}</h2>
        }
      }
    </ThemeContext.Consumer>
  </div>
}
export default HomeBanner

我們什麼使用setState

開發中我們並不能直接通過修改state的值來讓介面發生更新:

因為我們修改了state之後,希望React根據最新的State來重新渲染介面,但是這種方式的修改React並不知道資料發生了變化;

React並沒有實現類似於Vue2中的Object.defineProperty或者Vue3中的Proxy的方式來監聽資料的變化;

我們必須通過setState來告知React資料已經發生了變化;

疑惑:在元件中並沒有實現setState的方法,為什麼可以呼叫呢?

原因很簡單,setState方法是從Component中繼承過來的。

setState非同步更新

setState的更新是非同步的?

最終列印結果是Hello World;

可見setState是非同步的操作,我們並不能在執行完setState之後立馬拿到最新的state的結果

setState設計為非同步,可以顯著的提升效能;

如果每次呼叫 setState都進行一次更新,那麼意味著render函數會被頻繁呼叫,介面重新渲染,這樣效率是很低的;

最好的辦法應該是獲取到多個更新,之後進行批次更新;

如果同步更新了state,但是還沒有執行render函數,那麼state和props不能保持同步;

state和props不能保持一致性,會在開發中產生很多的問題;

如何獲取非同步的結果

式一:setState的回撥

setState接受兩個引數:第二個引數是一個回撥函數,這個回撥函數會在更新後會執行;

格式如下:setState(partialState, callback)

changeText() {
	this.setState({
		message: '你好'
	}), () => {
		console.log(this.state.message)
	}
}

當然我們也可以在生命週期函數

componentDidUpdate(precProps, provState, snapshot) {
	console.log(this.state.message)
}

setState一定是非同步的嗎(React18之前)

其實分成兩種情況:

在元件生命週期或React合成事件中,setState是非同步;

在setTimeout或者原生dom事件中,setState是同步;

setState預設是非同步的 (React18之後)

在React18之後,預設所有的操作都被放到了批次處理中(非同步處理)。

如果希望程式碼可以同步會拿到,則需要執行特殊的flushSync操作

import React, { Component } from 'react'
import { flushSync } from 'react-dom'
function Hello(props) {
  return <h2>{props.message}</h2>
}
export class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      message: "Hello World",
      counter: 0
    }
  }
  componentDidMount() {
    // 1.網路請求一: banners
    // 2.網路請求二: recommends
    // 3.網路請求三: productlist
  }
  changeText() {
    setTimeout(() => {
      // 在react18之前, setTimeout中setState操作, 是同步操作
      // 在react18之後, setTimeout中setState非同步操作(批次處理)
      flushSync(() => {
        this.setState({ message: "你好啊, 李銀河" })
      })
      console.log(this.state.message)
    }, 0);
  }
  increment() {
  }
  render() {
    const { message, counter } = this.state
    console.log("render被執行")
    return (
      <div>
        <h2>message: {message}</h2>
        <button onClick={e => this.changeText()}>修改文字</button>
        <h2>當前計數: {counter}</h2>
        <button onClick={e => this.increment()}>counter+1</button>

        <Hello message={message}/>
      </div>
    )
  }
}
export default App

到此這篇關於React Context與setState詳解使用方法的文章就介紹到這了,更多相關React Context與setState內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com