React Context与setState详解使用方法
_聪明勇敢有力气 人气:0React中的插槽(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
加载全部内容