Akses React Context di luar fungsi render

93

Saya mengembangkan aplikasi baru menggunakan React Context API yang baru, bukan Redux, dan sebelumnya, dengan Redux, ketika saya perlu mendapatkan daftar pengguna misalnya, saya cukup memanggil componentDidMounttindakan saya, tetapi sekarang dengan React Context, tindakan saya langsung di dalam Konsumen saya yang ada di dalam fungsi render saya, yang berarti bahwa setiap kali fungsi render saya dipanggil, tindakan saya akan dipanggil untuk mendapatkan daftar pengguna saya dan itu tidak baik karena saya akan melakukan banyak permintaan yang tidak perlu.

Jadi, bagaimana saya dapat memanggil hanya satu kali tindakan saya, seperti masuk componentDidMountalih-alih memanggil dalam render?

Sebagai contoh, lihat kode ini:

Misalkan saya membungkus semua saya Providersdalam satu komponen, seperti ini:

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>
    )
  }
}

Lalu saya meletakkan komponen Penyedia ini yang membungkus semua aplikasi saya, seperti ini:

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>
    )
  }
}

Sekarang, pada tampilan pengguna saya misalnya, akan menjadi seperti ini:

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>
    )
  }
}

Yang saya inginkan adalah ini:

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>
    )
  }
}

Tapi tentu saja contoh di atas tidak berfungsi karena getUserstidak ada di properti tampilan Pengguna saya. Apa cara yang benar untuk melakukannya jika ini memungkinkan?

Gustavo Mendonça
sumber

Jawaban:

144

EDIT: Dengan diperkenalkannya react-hooks di v16.8.0 , Anda dapat menggunakan konteks dalam komponen fungsional dengan memanfaatkan useContexthook

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

EDIT: Dari versi 16.6.0 dan seterusnya. Anda dapat menggunakan konteks dalam metode siklus hidup menggunakan 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

Sebelum versi 16.6.0, Anda dapat melakukannya dengan cara berikut

Untuk menggunakan Konteks dalam metode gaya hidup Anda, Anda harus menulis komponen Anda seperti

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>
)

Umumnya Anda akan mempertahankan satu konteks di Aplikasi Anda dan masuk akal untuk mengemas login di atas dalam HOC untuk digunakan kembali. Anda bisa menulisnya seperti

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>
    );
  };
};

dan kemudian Anda bisa menggunakannya seperti

export default withUserContext(User);
Shubham Khatri
sumber
Itu adalah cara untuk melakukannya! Tetapi saya membungkus komponen saya dengan hal-hal lain, sehingga kode akan menjadi semakin rumit melakukan cara ini. Jadi, menggunakan pustaka with-context dan dekoratornya membuat kode jauh lebih sederhana. Tapi terima kasih atas jawabannya!
Gustavo Mendonça
Itu tidak berhasil untuk saya, saya menggunakan github dengan konteks
PassionateDeveloper
1
Shubham Khatri benar dalam jawabannya, hanya ada kesalahan ketik kecil yang mencegah hal ini berjalan. Tanda kurung kurawal mencegah pengembalian implisit. Ganti saja dengan tanda kurung.
VDubs
1
Bisakah Anda menjelaskan cara menggunakan this.contextdengan banyak konteks di dalam komponen yang sama? Misalkan saya memiliki komponen yang perlu mengakses UserContext dan PostContext, bagaimana cara menanganinya? Apa yang dapat saya lihat adalah membuat konteks yang mencampurkan keduanya, tetapi itulah satu-satunya atau solusi yang lebih baik untuk ini?
Gustavo Mendonça
1
@ GustavoMendonça Anda hanya dapat berlangganan ke satu konteks menggunakan implementasi API konteks.ini. Jika Anda perlu membaca lebih dari satu, Anda harus mengikuti pola render prop
Shubham Khatri
3

Oke, saya menemukan cara untuk melakukan ini dengan batasan. Dengan with-contextpustaka saya berhasil memasukkan semua data konsumen saya ke dalam props komponen saya.

Tapi, untuk memasukkan lebih dari satu konsumen ke dalam komponen yang sama rumit untuk dilakukan, Anda harus menciptakan konsumen campuran dengan perpustakaan ini, yang membuat kode tidak elegan dan tidak produktif.

Tautan ke perpustakaan ini: https://github.com/SunHuawei/with-context

EDIT: Sebenarnya Anda tidak perlu menggunakan api multi konteks yang with-contextdisediakan, pada kenyataannya Anda dapat menggunakan api sederhana dan membuat dekorator untuk setiap konteks Anda dan jika Anda ingin menggunakan lebih dari satu konsumen dalam komponen Anda, cukup nyatakan di atas kelas Anda sebanyak dekorator yang Anda inginkan!

Gustavo Mendonça
sumber
1

Bagi saya, itu cukup untuk menambah .bind(this)acara. Seperti inilah tampilan Komponen saya.

// 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>
  }
}
Ballpin
sumber
1
kode ini terlihat jauh lebih sederhana, tetapi sebenarnya tidak mengakses nilai atau berinteraksi dengan instance RootStore sama sekali. dapatkah Anda menunjukkan contoh yang dibagi menjadi dua file dan pembaruan UI reaktif?
dcsan
-8

Anda harus meneruskan konteks dalam komponen induk yang lebih tinggi untuk mendapatkan akses sebagai props pada anak.

kprocks
sumber