จะอัปเดต React Context จากภายในองค์ประกอบลูกได้อย่างไร


98

ฉันมีการตั้งค่าภาษาในบริบทดังต่อไปนี้

class LanguageProvider extends Component {
  static childContextTypes = {
    langConfig: PropTypes.object,
  };

  getChildContext() {
    return { langConfig: 'en' };
  }

  render() {
    return this.props.children;
  }
}

export default LanguageProvider;

รหัสแอปพลิเคชันของฉันจะเป็นดังนี้

<LanguageProvider>
  <App>
    <MyPage />
  </App>
</LanguageProvider>

หน้าของฉันมีส่วนประกอบสำหรับเปลี่ยนภาษา

<MyPage>
  <LanguageSwitcher/>
</MyPage>

LanguageSwitcher ในการนี้MyPageจำเป็นต้องอัปเดตบริบทเพื่อเปลี่ยนภาษาเป็น 'jp' ดังต่อไปนี้

class LanguageSwitcher extends Component {
  static contextTypes = {
    langConfig: PropTypes.object,
  };

  updateLanguage() {
    //Here I need to update the langConfig to 'jp' 
  }

  render() {
    return <button onClick={this.updateLanguage}>Change Language</button>;
  }
}

export default LanguageSwitcher;

ฉันจะอัปเดตบริบทจากภายในคอมโพเนนต์ LanguageSwitcher ได้อย่างไร


คุณอ่านสิ่งนี้แล้วหรือยัง? facebook.github.io/react/docs/context.html#updating-contextบางทีนี่อาจเป็นสิ่งที่เหมาะกว่าสำหรับ state not context
azium

@azium ใช่ .. ในเอกสารนั้นบริบทจะได้รับการอัปเดตจากส่วนประกอบเองหรือมีการเพิ่มลิงก์บล็อกในเอกสารซึ่งมีบริบทที่ส่งผ่านเป็นอุปกรณ์ประกอบฉากไปยังผู้ให้บริการบริบทฉันต้องอัปเดตจากองค์ประกอบย่อย
mshameer

เอ่อไม่มีเอกสารบอกว่าอย่าใช้บริบทถ้าคุณต้องการอัปเดต “ อย่าทำ” ให้แม่น ขอย้ำอีกครั้งว่าใช้ state not context
azium

2
@LondonRob คุณกำลังมองหาคำตอบแบบไหน? IMO เนื้อหาของเอกสารดูดีสำหรับฉัน หากคุณต้องการตั้งค่าบริบทในลูกให้สร้างตัวตั้งค่าในองค์ประกอบของผู้ให้บริการและส่งต่อไปยังผู้บริโภคที่เป็นเด็ก จากนั้นเรียกตัวตั้งค่านั้นในผู้บริโภคลูกและตั้งค่าเป็นข้อมูลใดก็ได้ที่อยู่ในลูก ยังคงเป็นไปตามแนวคิดของ React ในการยกข้อมูลขึ้น
Andrew Li

2
@azium เพียงแค่แจ้งให้คนอื่น ๆ อ่านความคิดเห็นนี้ตลอดหลายปีต่อมา การอัปเดตบริบทจากองค์ประกอบลูกได้รับการสนับสนุนแล้วและค่อนข้างตรงไปตรงมา: hyp.is/FiP3mG6fEeqJiOfWzfKpgw/reactjs.org/docs/context.html
lustig

คำตอบ:


226

ใช้ตะขอ

Hooks ถูกนำมาใช้ใน 16.8.0 ดังนั้นรหัสต่อไปนี้ต้องใช้เวอร์ชันขั้นต่ำ 16.8.0 (เลื่อนลงสำหรับตัวอย่างส่วนประกอบของคลาส) CodeSandbox Demo

1. การตั้งค่าสถานะแม่สำหรับบริบทแบบไดนามิก

ประการแรกเพื่อให้มีบริบทแบบไดนามิกที่สามารถส่งผ่านไปยังผู้บริโภคได้ฉันจะใช้สถานะของผู้ปกครอง สิ่งนี้ทำให้มั่นใจได้ว่าฉันมีแหล่งความจริงเพียงแหล่งเดียว ตัวอย่างเช่นแอปหลักของฉันจะมีลักษณะดังนี้:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    ...
  );
};

languageถูกเก็บไว้ในรัฐ เราจะส่งผ่านทั้งสองอย่างlanguageและฟังก์ชัน setter setLanguageผ่านบริบทในภายหลัง

2. การสร้างบริบท

ต่อไปฉันสร้างบริบทภาษาดังนี้:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

ที่นี่ฉันกำลังตั้งค่าเริ่มต้นสำหรับlanguage('en') และsetLanguageฟังก์ชันที่ผู้ให้บริการบริบทจะส่งไปยังผู้บริโภค ค่าเหล่านี้เป็นเพียงค่าเริ่มต้นและฉันจะระบุค่าเมื่อใช้องค์ประกอบผู้ให้บริการในพาเรนAppต์

หมายเหตุ: LanguageContextยังคงเหมือนเดิมไม่ว่าคุณจะ

3. การสร้างผู้บริโภคตามบริบท

เพื่อให้ตัวสลับภาษาตั้งค่าภาษาควรมีการเข้าถึงฟังก์ชันตัวตั้งค่าภาษาผ่านบริบท อาจมีลักษณะดังนี้:

const LanguageSwitcher = () => {
  const { language, setLanguage } = useContext(LanguageContext);
  return (
    <button onClick={() => setLanguage("jp")}>
      Switch Language (Current: {language})
    </button>
  );
};

ที่นี่ฉันแค่ตั้งค่าภาษาเป็น 'jp' แต่คุณอาจมีตรรกะของคุณเองในการตั้งค่าภาษาสำหรับสิ่งนี้

4. ห่อผู้บริโภคในผู้ให้บริการ

ตอนนี้ฉันจะแสดงองค์ประกอบตัวสลับภาษาของฉันใน a LanguageContext.Providerและส่งผ่านค่าที่ต้องส่งผ่านบริบทไปยังระดับที่ลึกกว่า นี่คือลักษณะของผู้ปกครองของฉันApp:

const App = () => {
  const [language, setLanguage] = useState("en");
  const value = { language, setLanguage };

  return (
    <LanguageContext.Provider value={value}>
      <h2>Current Language: {language}</h2>
      <p>Click button to change to jp</p>
      <div>
        {/* Can be nested */}
        <LanguageSwitcher />
      </div>
    </LanguageContext.Provider>
  );
};

ตอนนี้เมื่อใดก็ตามที่คลิกตัวสลับภาษามันจะอัปเดตบริบทแบบไดนามิก

CodeSandbox Demo

การใช้ส่วนประกอบของคลาส

API บริบทล่าสุดเปิดตัวใน React 16.3 ซึ่งเป็นวิธีที่ยอดเยี่ยมในการมีบริบทแบบไดนามิก รหัสต่อไปนี้ต้องการเวอร์ชันขั้นต่ำ 16.3.0 CodeSandbox Demo

1. การตั้งค่าสถานะแม่สำหรับบริบทแบบไดนามิก

ประการแรกเพื่อให้มีบริบทแบบไดนามิกที่สามารถส่งผ่านไปยังผู้บริโภคได้ฉันจะใช้สถานะของผู้ปกครอง สิ่งนี้ทำให้มั่นใจได้ว่าฉันมีแหล่งความจริงเพียงแหล่งเดียว ตัวอย่างเช่นแอปหลักของฉันจะมีลักษณะดังนี้:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  ...
}

languageถูกเก็บไว้ในรัฐพร้อมกับวิธีการตั้งค่าภาษาที่คุณอาจเก็บนอกต้นไม้รัฐ

2. การสร้างบริบท

ต่อไปฉันสร้างบริบทภาษาดังนี้:

// set the defaults
const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
});

ที่นี่ฉันกำลังตั้งค่าเริ่มต้นสำหรับlanguage('en') และsetLanguageฟังก์ชันที่ผู้ให้บริการบริบทจะส่งไปยังผู้บริโภค ค่าเหล่านี้เป็นเพียงค่าเริ่มต้นและฉันจะระบุค่าเมื่อใช้องค์ประกอบผู้ให้บริการในพาเรนAppต์

3. การสร้างผู้บริโภคตามบริบท

เพื่อให้ตัวสลับภาษาตั้งค่าภาษาควรมีการเข้าถึงฟังก์ชันตัวตั้งค่าภาษาผ่านบริบท อาจมีลักษณะดังนี้:

class LanguageSwitcher extends Component {
  render() {
    return (
      <LanguageContext.Consumer>
        {({ language, setLanguage }) => (
          <button onClick={() => setLanguage("jp")}>
            Switch Language (Current: {language})
          </button>
        )}
      </LanguageContext.Consumer>
    );
  }
}

ที่นี่ฉันแค่ตั้งค่าภาษาเป็น 'jp' แต่คุณอาจมีตรรกะของคุณเองในการตั้งค่าภาษาสำหรับสิ่งนี้

4. ห่อผู้บริโภคในผู้ให้บริการ

ตอนนี้ฉันจะแสดงองค์ประกอบตัวสลับภาษาของฉันใน a LanguageContext.Providerและส่งผ่านค่าที่ต้องส่งผ่านบริบทไปยังระดับที่ลึกกว่า นี่คือลักษณะของผู้ปกครองของฉันApp:

class App extends Component {
  setLanguage = language => {
    this.setState({ language });
  };

  state = {
    language: "en",
    setLanguage: this.setLanguage
  };

  render() {
    return (
      <LanguageContext.Provider value={this.state}>
        <h2>Current Language: {this.state.language}</h2>
        <p>Click button to change to jp</p>
        <div>
          {/* Can be nested */}
          <LanguageSwitcher />
        </div>
      </LanguageContext.Provider>
    );
  }
}

ตอนนี้เมื่อใดก็ตามที่คลิกตัวสลับภาษามันจะอัปเดตบริบทแบบไดนามิก

CodeSandbox Demo


อะไรคือจุดประสงค์ของค่าเริ่มต้นที่คุณเริ่มต้นบริบทด้วย? ค่าเริ่มต้นเหล่านั้นไม่ได้ถูกแทนที่ด้วยProviderหรือไม่?
ecoe

@ecoe ถูกต้องอย่างไรก็ตามในกรณีที่ผู้ให้บริการไม่ผ่านvalueผู้บริโภคจะใช้ค่าเริ่มต้น
Divyanshu Maithani

1
เหตุใดบริบทจึงถูก จำกัด ให้กำหนด / รับค่าง่ายๆเพียงค่าเดียว .... ซึ่งดูเหมือนไม่มีประสิทธิภาพมาก ตัวอย่างที่ดีกว่าคือการเน้นบริบทที่มีค่าเริ่มต้นเป็นวัตถุและเพื่ออัปเดตวัตถุตามนั้น
AlxVallejo

เป็นไปได้ที่จะใช้บริบทสำหรับสิ่งที่คุณกำลังกล่าวถึง ตัวอย่างในคำตอบคือผลตอบแทนตามคำถาม ดังนั้นจึงมีเพียงค่าเดียวสำหรับความกะทัดรัด
Divyanshu Maithani

1
typescript บ่นในกรณีของฉันถ้า setLanguage ไม่มีพารามิเตอร์ setLanguage: (language: string) => {}เหมาะกับฉัน
alex351

48

คำตอบข้างต้นโดย Maithani เป็นวิธีแก้ปัญหาที่ยอดเยี่ยม แต่เป็นส่วนประกอบของคลาสโดยไม่ต้องใช้ตะขอ เนื่องจาก React แนะนำให้ใช้ส่วนประกอบและ hooks ที่ใช้งานได้ดังนั้นฉันจะใช้กับ useContext และ useState hooks นี่คือวิธีที่คุณสามารถอัปเดตบริบทจากภายในองค์ประกอบลูก

LanguageContextMangement.js

import React, { useState } from 'react'

export const LanguageContext = React.createContext({
  language: "en",
  setLanguage: () => {}
})

export const LanguageContextProvider = (props) => {

  const setLanguage = (language) => {
    setState({...state, language: language})
  }

  const initState = {
    language: "en",
    setLanguage: setLanguage
  } 

  const [state, setState] = useState(initState)

  return (
    <LanguageContext.Provider value={state}>
      {props.children}
    </LanguageContext.Provider>
  )
}

App.js

import React, { useContext } from 'react'
import { LanguageContextProvider, LanguageContext } from './LanguageContextManagement'

function App() {

  const state = useContext(LanguageContext)

  return (
    <LanguageContextProvider>
      <button onClick={() => state.setLanguage('pk')}>
        Current Language is: {state.language}
      </button>
    </LanguageContextProvider>
  )
}

export default App

1
ฉันกำลังทำสิ่งนี้และฟังก์ชัน set ของฉันในองค์ประกอบลูกของฉันเป็นสิ่งที่เราประกาศในตอนแรกเสมอเมื่อสร้างบริบท:() => {}
Alejandro Corredor

4
เพื่อชี้แจงฉันคิดว่าในตัวอย่างของคุณstate.setLanguage('pk')จะไม่ทำอะไรเลยเนื่องจากconst state = useContext(LanguageContext)อยู่นอกไฟล์LanguageContextProvider. ฉันแก้ไขปัญหาของฉันโดยการเลื่อนผู้ให้บริการขึ้นหนึ่งระดับจากนั้นใช้useContextกับเด็กที่อยู่ด้านล่างหนึ่งระดับ
Alejandro Corredor

2
<LanguageContext.Consumer> {value => /* access your value here */} </LanguageContext.Consumer>ในกรณีที่คุณไม่ต้องการที่จะย้ายผู้ให้บริการบริบทหนึ่งระดับของคุณยังสามารถใช้ผู้บริโภคบริบทเช่นนี้
Mateen Kiani

3
ฉันชอบวิธีการจัดระเบียบไฟล์ LanguageContextMangement.js มาก นั่นเป็นวิธีที่สะอาดในการทำสิ่งต่างๆในความคิดของฉันและฉันจะเริ่มทำสิ่งนั้นทันที ขอบคุณ!
lustig

2
ขอบคุณสำหรับความชื่นชมมันเป็นกำลังใจให้ฉันทำงานแบบนี้ต่อไป!
Mateen Kiani

2

วิธีแก้ปัญหาที่ค่อนข้างง่ายวิธีหนึ่งคือการตั้งค่าสถานะในบริบทของคุณโดยรวมวิธีการ setState ในผู้ให้บริการของคุณดังนี้:

return ( 
            <Context.Provider value={{
              state: this.state,
              updateLanguage: (returnVal) => {
                this.setState({
                  language: returnVal
                })
              }
            }}> 
              {this.props.children} 
            </Context.Provider>
        )

และในผู้บริโภคของคุณโทร updateLanguage ดังนี้:

// button that sets language config
<Context.Consumer>
{(context) => 
  <button onClick={context.updateLanguage({language})}> 
    Set to {language} // if you have a dynamic val for language
  </button>
<Context.Consumer>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.