เข้าถึง React Context นอกฟังก์ชัน render


93

ฉันกำลังพัฒนาแอปใหม่โดยใช้ React Context API ใหม่แทน Redux และก่อนหน้านี้Reduxเมื่อฉันต้องการรับรายชื่อผู้ใช้เช่นฉันเรียกcomponentDidMountสิ่งที่ทำ แต่ตอนนี้ด้วย React Context การกระทำของฉันอยู่ภายใน ผู้บริโภคของฉันซึ่งอยู่ในฟังก์ชันการเรนเดอร์ของฉันซึ่งหมายความว่าทุกครั้งที่เรียกใช้ฟังก์ชันการเรนเดอร์ระบบจะเรียกการดำเนินการของฉันเพื่อรับรายชื่อผู้ใช้ของฉันและนั่นไม่ดีเพราะฉันจะดำเนินการตามคำขอที่ไม่จำเป็นจำนวนมาก

ดังนั้นฉันจะเรียกเพียงครั้งเดียวการกระทำของฉันเช่นcomponentDidMountแทนที่จะเรียกในการแสดงผลได้อย่างไร

เพื่อเป็นตัวอย่างให้ดูที่รหัสนี้:

สมมติว่าฉันกำลังรวมProvidersองค์ประกอบทั้งหมดไว้ในองค์ประกอบเดียวดังนี้:

import React from 'react';

import UserProvider from './UserProvider';
import PostProvider from './PostProvider';

export default class Provider extends React.Component {
  render(){
    return(
      <UserProvider>
        <PostProvider>
          {this.props.children}
        </PostProvider>
      </UserProvider>
    )
  }
}

จากนั้นฉันใส่องค์ประกอบผู้ให้บริการนี้ห่อแอปทั้งหมดของฉันดังนี้:

import React from 'react';
import Provider from './providers/Provider';
import { Router } from './Router';

export default class App extends React.Component {
  render() {
    const Component = Router();
    return(
      <Provider>
        <Component />
      </Provider>
    )
  }
}

ตอนนี้ที่ผู้ใช้ของฉันดูตัวอย่างเช่นมันจะเป็นดังนี้:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  render(){
    return(
      <UserContext.Consumer>
        {({getUsers, users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

สิ่งที่ฉันต้องการคือ:

import React from 'react';
import UserContext from '../contexts/UserContext';

export default class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    return(
      <UserContext.Consumer>
        {({users}) => {
          getUsers();
          return(
            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
          )
        }}
      </UserContext.Consumer>
    )
  }
}

แต่แน่นอนว่าตัวอย่างข้างต้นไม่ได้ผลเพราะgetUsersไม่ได้อยู่ในอุปกรณ์ประกอบการดูผู้ใช้ของฉัน อะไรคือวิธีที่ถูกต้องที่จะทำถ้าเป็นไปได้ทั้งหมด?

คำตอบ:


145

แก้ไข:ด้วยการแนะนำ react-hooks ในv16.8.0คุณสามารถใช้บริบทในส่วนประกอบที่ใช้งานได้โดยใช้useContexthook

const Users = () => {
    const contextValue = useContext(UserContext);
    // rest logic here
}

แก้ไข:ตั้งแต่เวอร์ชัน16.6.0เป็นต้นไป คุณสามารถใช้บริบทในวิธีวงจรชีวิตโดยใช้this.contextlike

class Users extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* perform a side-effect at mount using the value of UserContext */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* render something based on the value of UserContext */
  }
}
Users.contextType = UserContext; // This part is important to access context values

ก่อนหน้าเวอร์ชัน 16.6.0 คุณสามารถทำได้ในลักษณะต่อไปนี้

ในการใช้ Context ในวิธีการใช้ชีวิตของคุณคุณจะต้องเขียนส่วนประกอบของคุณเช่น

class Users extends React.Component {
  componentDidMount(){
    this.props.getUsers();
  }

  render(){
    const { users } = this.props;
    return(

            <h1>Users</h1>
            <ul>
              {users.map(user) => (
                <li>{user.name}</li>
              )}
            </ul>
    )
  }
}
export default props => ( <UserContext.Consumer>
        {({users, getUsers}) => {
           return <Users {...props} users={users} getUsers={getUsers} />
        }}
      </UserContext.Consumer>
)

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

import UserContext from 'path/to/UserContext';

const withUserContext = Component => {
  return props => {
    return (
      <UserContext.Consumer>
        {({users, getUsers}) => {
          return <Component {...props} users={users} getUsers={getUsers} />;
        }}
      </UserContext.Consumer>
    );
  };
};

จากนั้นคุณสามารถใช้มันได้เช่น

export default withUserContext(User);

นั่นคือวิธีที่ต้องทำ! แต่ฉันกำลังห่อส่วนประกอบของฉันกับสิ่งอื่นดังนั้นโค้ดจะซับซ้อนขึ้นเรื่อย ๆ เมื่อทำแบบนี้ ดังนั้นการใช้ไลบรารีที่มีบริบทและมัณฑนากรทำให้โค้ดง่ายขึ้นมาก แต่ขอบคุณสำหรับคำตอบ!
Gustavo Mendonça

มันไม่ได้ผลสำหรับฉันฉันใช้ github ที่มีบริบท
PassionateDeveloper

1
Shubham Khatri ถูกต้องในคำตอบของเขามีการพิมพ์ผิดเล็กน้อยที่ป้องกันไม่ให้สิ่งนี้ทำงาน วงเล็บปีกกาป้องกันการกลับมาโดยปริยาย เพียงแค่แทนที่ด้วยวงเล็บ
VDubs

1
คุณสามารถอธิบายวิธีใช้this.contextกับหลายบริบทภายในส่วนประกอบเดียวกันได้หรือไม่? สมมติว่าฉันมีส่วนประกอบที่ต้องการเข้าถึง UserContext และ PostContext จะจัดการอย่างไร สิ่งที่ฉันเห็นคือการสร้างบริบทผสมทั้งสองอย่าง แต่นั่นเป็นทางออกเดียวหรือดีกว่าสำหรับสิ่งนี้
Gustavo Mendonça

1
@ GustavoMendonçaคุณสามารถสมัครใช้งานบริบทเดียวได้โดยใช้การใช้ this.context API หากคุณต้องการอ่านมากกว่าหนึ่งรายการคุณต้องทำตามรูปแบบการแสดงผล
Shubham Khatri

3

โอเคฉันพบวิธีที่ทำได้โดยมีข้อ จำกัด ด้วยwith-contextไลบรารีฉันจัดการเพื่อแทรกข้อมูลผู้บริโภคทั้งหมดของฉันลงในอุปกรณ์ประกอบฉากของฉัน

แต่ในการแทรกผู้บริโภคมากกว่าหนึ่งรายลงในส่วนประกอบเดียวกันนั้นมีความซับซ้อนในการทำคุณต้องสร้างผู้บริโภคแบบผสมผสานด้วยไลบรารีนี้ซึ่งทำให้โค้ดไม่หรูหราและไม่ได้ประสิทธิผล

ลิงก์ไปยังห้องสมุดนี้: https://github.com/SunHuawei/with-context

แก้ไข: จริงๆแล้วคุณไม่จำเป็นต้องใช้ API หลายบริบทที่with-contextให้มาในความเป็นจริงคุณสามารถใช้ api แบบธรรมดาและสร้างมัณฑนากรสำหรับแต่ละบริบทของคุณและหากคุณต้องการใช้ผู้บริโภคมากกว่าหนึ่งรายในส่วนประกอบของคุณเพียงแค่ ประกาศเหนือชั้นเรียนของคุณให้มากที่สุดเท่าที่คุณต้องการ!


1

ในส่วนของฉันก็เพียงพอแล้วที่จะเพิ่ม.bind(this)ในงาน นี่คือลักษณะของส่วนประกอบของฉัน

// Stores File
class RootStore {
   //...States, etc
}
const myRootContext = React.createContext(new RootStore())
export default myRootContext;


// In Component
class MyComp extends Component {
  static contextType = myRootContext;

  doSomething() {
   console.log()
  }

  render() {
    return <button onClick={this.doSomething.bind(this)}></button>
  }
}

1
รหัสนี้ดูง่ายกว่ามาก แต่ไม่ได้เข้าถึงค่าหรือโต้ตอบกับอินสแตนซ์ RootStore เลย คุณสามารถแสดงตัวอย่างที่แบ่งออกเป็นสองไฟล์และการอัปเดต UI แบบตอบสนองได้หรือไม่
dcsan

-8

คุณต้องส่งบริบทในองค์ประกอบหลักที่สูงขึ้นเพื่อเข้าถึงเป็นอุปกรณ์ประกอบฉากในเด็ก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.