React Hook--useContext

461 阅读4分钟

useContext

React中组件之间传递参数的方式是props,假如我们在父级组件中定义了某些状态,而这些状态需要在该组件深层次嵌套的子组件中被使用的话就需要将这些状态以props的形式层层传递,这就比较麻烦。为了解决这个问题,React允许我们在类组件使用Context来在父级组件和底下任意层次的子组件之间传递状态。在函数组件中我们可以使用useContext函数来使用Context。

产生useContext的原因

// userCtx.js
import React from "react";
// 创建上下文对象,提供者
const userContext=React.createContext();
export default userContext;
import Des from './Des.jsx';
import UserContext from './userCtx.js';

export default function App() {
  let [obj,setObj]=useState({msg:'app父级组件数据',id:67})
  return (
    <div>
      <h1>App组件</h1>
      <UserContext.Provider value={obj}>
        <Des></Des>
      </UserContext.Provider>
    </div>
  )
}

import First from './First.jsx'

export default function Des(props) {
    
      return (
        <div>
            <h2>Des组件</h2>
            <First></First>
        </div>
      )
}


import React from 'react'
import UserCtx from './userCtx.js'
export default function First() {
    return (
        <div>
            <h3>First组件</h3>
            <UserCtx.Consumer>
                {
                    (ctx) => {
                        return (
                            <div>
                                <p>{ctx.msg}</p>
                                <p>{ctx.id}</p>
                            </div>
                        )
                    }
                }
            </UserCtx.Consumer>
        </div>
    )
}

image.png

这样函数组件中只能在模板中渲染父级组件的数据,不能在函数内部中使用,因为ctx该形参只能在return的箭头函数内部能访问。此时就需要引入useContext这个Hook函数。

useContext的使用

const value = useContext(MyContext)

useContext接收一个context对象为参数,该context对象是由React.createContext函数生成的。useContext的返回值是当前context的值。一旦在某个组件里面使用了useContext这就相当于该组件订阅了这个context的变化,当context值发生变化时,使用到该context的子组件就会被触发重渲染,且它们会拿到context的最新值。

import { useState,useEffect } from 'react'
import Des from './Des.jsx';
import UserContext from './userCtx.js';

export default function App() {
  let [obj,setObj]=useState({msg:'app父级组件数据',id:67})
  return (
    <div>
      <h1>App组件</h1>
      <p>{obj.msg}</p>
      <UserContext.Provider value={[obj,setObj]}>
        <Des></Des>
      </UserContext.Provider>
    </div>
  )
}

import First from './First.jsx'

export default function Des(props) {
    
      return (
        <div>
            <h2>Des儿子组件</h2>
            <First></First>
        </div>
      )
}

import {useContext} from 'react'
import UserCtx from './userCtx.js'
export default function First() {
    let userctx=useContext(UserCtx);
    let look=()=>{
        console.log(userctx,666666);
    };
    let change=()=>{
        userctx[0].msg='孙子组件修改了app父级组件的数据';
        userctx[1]({...userctx[0]})
    }
    return (
        <div>
            <h3>First孙子组件</h3>
            <button onClick={look}>look</button>
            <button onClick={change}>change</button>
            <p>{userctx[0].msg}</p>
            <p>{userctx[0].id}</p>
        </div>
    )
}

2.gif

useContext使用时造成的无用渲染

如果一个函数组件中使用了useContext(Context)表示它就订阅了这个上下文对象Context的变化,这样Context.Provider的value发生变化的时候,这个组件就会被重新渲染。

如果把很多不同的数据都放在同一个上下文对象Context中,实际上不同的子组件可能只关心这个Context的某一部分数据,而当Context中任意值发生变化的时候,无论这些组件有没有使用这些数据它们都会被重新渲染,这些无用渲染就可能会造成一些性能问题。

import { useState } from 'react'
import Des from './Des.jsx';
import UserContext from './userCtx.js';

export default function App() {
  let [obj,setObj]=useState({info:{msg:'app父级组件数据',id:67},description:{team:'6ara 4ever',site:'yyds'}});
  return (
    <div>
      <h1>App组件</h1>
      <p>{obj.info.msg}</p>
      <UserContext.Provider value={[obj,setObj]}>
        <Des></Des>
      </UserContext.Provider>
    </div>
  )
}

import {useContext} from 'react'
import First from './First.jsx'
import UserContext from './userCtx.js';
export default function Des(props) {
    let [state,setState]=useContext(UserContext);
    let changedescription=()=>{
      state.description.site='tiamo';
      setState({...state});
    };
      return (
        <div>
            <h2>Des儿子组件</h2>
            <p>{state.description.team}--{state.description.site}</p>
            <button onClick={changedescription}>修改description</button>
            <First></First>
        </div>
      )
}

import {useContext} from 'react'
import UserCtx from './userCtx.js'
export default function First() {
    console.log('First组件运行进行渲染')
    let [ctx,changeCtx]=useContext(UserCtx);
    let change=()=>{
        ctx.info.msg='孙子组件修改了app父级组件的数据';
        changeCtx({...ctx})
    }
    return (
        <div>
            <h3>First孙子组件</h3>
            <button onClick={change}>change</button>
            <p>{ctx.info.msg}</p>
            <p>{ctx.info.id}</p>
        </div>
    )
}

2.gif

在Des组件中修改了description属性并没有修改First组件使用的info属性,父级Des组件的修改运行刷新页面的函数导致了Des组件中子级First组件的函数也重新运行了,所以就造成无用的渲染。

useContext使用时无用渲染的解决方法

第一种:拆分Context

将不需要同时改变的Context拆分成不同的Context,这样子组件只会订阅那些它们需要订阅的Context从而避免无用的重渲染。只有修改了Context所使用到的组件才会刷新

拆分组件需要将提供者Provider标签嵌套
import { useState } from 'react'
import Des from './Des.jsx';
import Infoctx from './Ctx/Infoctx.js'
import Descriptionctx from './Ctx/Descriptionctx.js'

export default function App() {
  let [info, setInfo] = useState({ msg: 'app父级组件数据', id: 67 });
  let [description, setDescription] = useState({ team: '6ara 4ever', site: 'yyds' })
  return (
    <div>
      <h1>App组件</h1>
      <Descriptionctx.Provider value={[description, setDescription]}>
        <Infoctx.Provider value={[info, setInfo]}>
          <Des></Des>
        </Infoctx.Provider>
      </Descriptionctx.Provider>
    </div>
  )
}


import {useContext} from 'react'
import Descriptionctx from './Ctx/Descriptionctx.js'
export default function Des(props) {
    let [state,setState]=useContext(Descriptionctx);
    let changedescription=()=>{
      state.site='tiamo';
      setState({...state});
    };
      return (
        <div>
            <h2>Des儿子组件</h2>
            <p>{state.team}--{state.site}</p>
            <button onClick={changedescription}>修改description</button>
        </div>
      )
}

第二种:使用useMemo来优化消耗性能的组件

import { useState } from 'react'
import Des from './Des.jsx';
import Infoctx from './Ctx/Infoctx.js'
export default function App() {
  let [state, setState] = useState({info:{ msg: 'app父级组件数据', id: 67 },description:{ team: '6ara 4ever', site: 'yyds' }});
  return (
    <div>
      <h1>App组件</h1>
        <Infoctx.Provider value={[state, setState]}>
          <Des></Des>
        </Infoctx.Provider>
    </div>
  )
}


import {useContext,useMemo} from 'react'
import First from './First.jsx'
import Infoctx from './Ctx/Infoctx.js'
function MeMoDes(){
  let [state,setState]=useContext(Infoctx);
  let Des=()=>{
    console.log('Des组件运行进行渲染');
    let changedescription=()=>{
      state.description.site='tiamo';
      state=JSON.parse(JSON.stringify(state))
      setState(state);
  };
    return (
      <div>
            <h2>Des儿子组件</h2>
            <p>{state.description.team}--{state.description.site}</p>
            <button onClick={changedescription}>修改description</button>
            <First></First>
        </div>
    )
  }
  return useMemo(Des,[state.description])
}
export default MeMoDes


import {useContext} from 'react'
import Infoctx from './Ctx/Infoctx.js'
export default function First() {
    console.log('First组件运行进行渲染')
    let [ctx,changeCtx]=useContext(Infoctx);
    let change=()=>{
        ctx.info.msg='孙子组件修改了app父级组件的数据';
        changeCtx({...ctx})
    }
    return (
        <div>
            <h3>First孙子组件</h3>
            <button onClick={change}>change</button>
            <p>{ctx.info.msg}</p>
            <p>{ctx.info.id}</p>
        </div>
    )
}

2.gif

只有修改了state.description,Des组件才会运行进行渲染