• React-18(组件化开发) -- 插槽 Context的应用场景 setState(18之前与18的对比)


    React

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    user-context.js

    import React from "react"
    
    // 1.创建一个Context
    const UserContext = React.createContext()
    
    export default UserContext
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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传递数据 */}
            {/* 
            
             */}
    
            {/* 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    我们什么使用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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当然我们也可以在生命周期函数

    componentDidUpdate(precProps, provState, snapshot) {
    	console.log(this.state.message)
    }
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    【白话科普】从“熊猫烧香”聊聊计算机病毒
    kafka笔记(二):生产者-同步发送/异步发送/生产者分区/数据可靠性/数据去重/消息发送流程
    腾讯云三年轻量2核4G5M服务器优惠价格(3年566元)
    Asp .Net Core 系列:集成 CORS跨域配置
    特征缩放是强制性的吗? 什么时候使用标准化? 什么时候使用归一化?数据的分布会发生什么变化?对异常值有什么影响?模型的准确性会提高吗?
    重学设计模式之-组合模式-mybatis-SqlNode使用组合模式
    Day47 代码随想录打卡|二叉树篇---最大二叉树
    LeetCode【121. 买卖股票的最佳时机】
    牛客网SQL基础强化
    计算机网络篇之MAC地址
  • 原文地址:https://blog.csdn.net/weixin_65402230/article/details/128064037