React: Bagaimana reaksi memecahkan konteks bersarang?

Dibuat pada 18 Jan 2019  ·  38Komentar  ·  Sumber: facebook/react

Saya memiliki banyak konteks dan saya harus menulis dengan cara ini, sangat jelek! Itu menghalangi pekerjaan saya sekarang. Desain seperti itu membuatnya hampir tidak bisa digunakan.

<context1.Provider value={value1}>
  <context2.Provider value={value2}>
    <context3.Provider value={value3}>
      <context4.Provider value={value4}>
        <context5.Provider value={value5}>

        </context5.Provider>
      </context4.Provider>
    </context3.Provider>
  </context2.Provider>
</context1.Provider>
<context1.Consumer>
  {value1 => <context2.Consumer>
    {value2 => <context3.Consumer>
      {value3 => <context4.Consumer>
        {value4 => <context5.Consumer>
          {value5 => (
            null
          )}
        </context5.Consumer>}
      </context4.Consumer>}
    </context3.Consumer>}
  </context2.Consumer>}
</context1.Consumer>
Question

Komentar yang paling membantu

@ 0xorial Anda tidak benar-benar perlu memiliki komponen untuk itu, karena <>> hanyalah sebuah pemanggilan fungsi React.createElement. Jadi Anda bisa menyederhanakannya menjadi fungsi tulis seperti:

const compose = (contexts, children) =>
  contexts.reduce((acc, [Context, value]) => {
    return <Context.Provider value={value}>{acc}</Context.Provider>;
  }, children);

dan gunakan sebagai:

import Context1 from './context1';
import Context2 from './context2';
import Context3 from './context3';
...
import Context15 from './context15';

const MyComponent = (props) => {
  // const value1..15 = ... get the values from somewhere ;

  return compose(
    [
      [Context1, value1],
      [Context2, value2],
      [Context3, value3],
      ...
      [Context15, value15],
    ],
    <SomeSubComponent/>
  );
}

Semua 38 komentar

API Hooks yang akan datang menyediakan cara berbeda untuk menggunakan konteks.

https://reactjs.org/docs/hooks-reference.html#usecontext

Terima kasih. Tapi bagaimana dengan provider?

Saya akan jujur. Jika Anda menghadapi implementasi semacam ini, maka desain arsitektur Anda tampak buruk dan Anda mungkin tidak boleh menggunakan konteks React.

Tidak, saya sedang mendesain wadah toko baru. Ini perlu bekerja dengan konteks dalam bereaksi.
https://github.com/rabbitooops/rako

Apa hubungan perpustakaan itu dengan 5 lapisan penyedia / konsumen?

Karena itu mendukung injeksi banyak toko untuk bereaksi daripada solusi toko tunggal seperti Redux.

Dalam hal ini, penanganan konteks sekarang ada pada pengguna perpustakaan, dan lebih sedikit pada perpustakaan. Bagaimana mereka memanfaatkan fitur-fiturnya terserah mereka, dan jika mereka menginginkan semua penyedia di satu tempat (yang mengalahkan tujuan memiliki banyak toko), maka itulah pilihan mereka. Idealnya, solusi beberapa toko akan diterapkan pada pemisahan yang berbeda dalam aplikasi, jadi konteks bersarang seperti ini jauh lebih jarang.

Saya setidaknya 2 sen.

Jadi API konteks hanya ramah untuk solusi penyimpanan tunggal seperti redux.

Tidak semuanya. Tetapi juga sulit untuk mendiskusikan masalah Anda tanpa contoh yang lebih realistis. Silakan buat satu?

Bayangkan ada tiga tema toko, pengguna dan penghitung.

function theme(getState) {
  return {
    color: 'white',
    setColor(color) {
      this.setState({color})
    }
  }
}

function user(getState) {
  return {
    name: '',
    setName(name) {
      this.setState({name})
    }
  }
}

function counter(getState) {
  return {
    value: 0,
    increment() {
      const {value} = getState()
      this.setState({value: value + 1})
    }
  }
}

const [themeStore, userStore, counterStore] = createStores(theme, user, counter)
const [themeContext, userContext, counterContext] = createContexts(themeStore, userStore, counterStore)


class App extends React.Component {
  render() {
    return (
      <themeContext.StoreProvider>
        <userContext.StoreProvider>
          <counterContext.StoreProvider>

            <Child />

          </counterContext.StoreProvider>
        </userContext.StoreProvider>
      </themeContext.StoreProvider>
    )
  }
}

class Child extends React.Component {
  static contextType = [themeContext, userContext]
  render() {
    const [theme, user] = this.context

    /* ... */
  }
}

Apa sintaks ideal Anda?

Gunakan context.write, dukung penggunaan banyak konteks tanpa bersarang.

Mengkonsumsi banyak konteks tanpa bersarang sudah didukung. (Dengan Hooks.)

Context.write memiliki RFC yang terbuka untuk itu. Kami tidak tahu apakah itu akan berhasil karena ini menimbulkan beberapa pertanyaan yang sangat rumit. Tetapi saat RFC terbuka, saya tidak yakin apa yang dapat ditindaklanjuti dalam masalah ini. Apakah Anda memiliki sesuatu untuk ditambahkan selain apa yang sudah ada dalam motivasi RFC?

Saya ingin mengajukan pertanyaan. Mengapa tidak bereaksi mendukung mengkonsumsi banyak konteks di kelas? Sepertinya API hooks memiliki banyak masalah yang harus dipecahkan dan saat ini sangat tidak stabil.😨🧐😥🤕

static contextType = [themeContext, userContext]
const [theme, user] = this.context

Saya akan menerapkan StoreProviders yang dapat menyarangkan banyak konteks secara otomatis.

const StoreProviders = constructStoreProviders(...storeContexts)

<StoreProviders>
  <Child />
</StoreProviders>

Terima kasih atas bantuan Anda Dan! :)

@rabbitooops Masalah apa sebenarnya yang terkait dengan pengait yang Anda miliki? Saya menggunakan pengait dalam produksi dan mereka bekerja dengan baik untuk tim saya.

Dan bagaimana dengan ini? Aman menggunakan kail sekarang. @gaul

// A library
function useStoreProviders(Component, ...contexts) {
  contexts.forEach(context => Component.useProvider(context, someValue))
}

// User code
function App(props) {
  const [theme] = useState('white')

  // Safe to use `component hook`.
  App.useProvider(themeContext, theme)
  App.useShouldComponentUpdate(() => {})

  // Meanwhile, library can also use `component hook`.
  useStoreProviders(App, storeContext1, storeContext2, storeContext3)

  // Normal hook can't use `component hook`.
  customHook()

  /* ... */
}

<App />

decorateBeforeRender(App)

@rabbitooops Bagaimana jika menggunakan satu penyimpanan dan Simbol sebagai kunci untuk meniru multi-lapisan penyimpanan?

data Store = Leaf Object | C Store Store

Atau dengan cara yang tidak sempurna di javascript:

const LEFT = Symbol('LEFT')
const RIGHT = Symbol('RIGHT')
function createLeafStore = return new Store({});
function createStore(leftChild :: Store, rightChild :: Store) {
  return new Store({[LEFT]: leftChild, [Right]: rightChild})
}

@TrySound Berbeda: App.useProvider

@zhujinxuan Maaf, saya tidak bisa mendapatkan Anda.

@zhujinxuan Anda dapat menggunakan Unstated , contoh:

<Subscribe to={[AppContainer, CounterContainer, ...]}>
  {(app, counter, ...) => (
    <Child />
  )}
</Subscribe>

Sepertinya hooks API memiliki banyak masalah yang harus diselesaikan dan saat ini sangat tidak stabil

Kami sedang mempersiapkannya untuk rilis dalam satu atau dua minggu - tidak yakin mengapa Anda menyimpulkannya. Mereka akan segera siap meskipun jika Anda ingin aman, harap tunggu sampai rilis stabil.

Dan bagaimana dengan ini?

Memanggil Hooks dalam satu putaran (seperti yang Anda lakukan di forEach ) umumnya tidak diperbolehkan. Cara ini mudah menyebabkan masalah.

useStoreProviders

Baik useProvider dan useShouldComponentUpdate bermasalah karena Hooks (itulah sebabnya React tidak memilikinya). Lihat tanggapan saya di https://github.com/facebook/react/issues/14534#issuecomment -455411307.


Secara keseluruhan, saya berjuang untuk memahami maksud dari masalah ini.

Mengkonsumsi banyak konteks diselesaikan dengan useContext Hook. Kami tidak menyarankan untuk "mengotomatiskan" dengan array karena ini membuatnya terlalu mudah untuk menulis komponen yang berlangganan terlalu banyak konteks dan merender ulang terlalu sering. Anda harus memiliki pemahaman yang jelas tentang konteks mana yang didengarkan komponen, yang diberikan oleh useContext API kepada Anda. Jika harus, Anda dapat menulis useMyContexts() Hook yang secara eksplisit menggunakan konteks tertentu. Saya hanya tidak merekomendasikan membuatnya dinamis seperti yang Anda lakukan karena jika panjang array berubah, itu bisa rusak.

Menempatkan beberapa penyedia dapat dilihat sebagai "boilerplate" dan pada akhirnya kami mungkin memiliki solusi untuk ini. Tapi saya juga tidak mengerti mengapa Anda melihatnya sebagai masalah besar. Contoh di utas ini tidak cukup realistis untuk menjelaskan masalahnya kepada saya. Saya tidak melihat sesuatu yang buruk dengan menempatkan beberapa lapisan JSX di suatu tempat di bagian atas aplikasi. Cukup yakin Anda memiliki lebih dalam div bersarang di sebagian besar komponen dan itu tidak terlalu menyakitkan.

Saya akan menutup ini karena saya pikir saya sudah menjawab poin-poin ini, dan diskusi berjalan berputar-putar. Jika ada sesuatu yang hilang beri tahu saya.

OT: @gaearon , apakah ada rencana untuk menambahkan sesuatu seperti useRender atau sesuatu untuk lebih mengontrol rendering? misalnya:

useRender(() => <div />, [...props])

Argumen kedua memiliki peran yang sama yaitu useEffect hook.

useMemo adalah temanmu.

Lihat cuplikan kedua di https://reactjs.org/docs/hooks-faq.html#how -to-memoize-calculator.

Saya berakhir dengan kode seperti itu:

function provider<T>(theProvider: React.Provider<T>, value: T) {
   return {
      provider: theProvider,
      value
   };
}

function MultiProvider(props: {providers: Array<{provider: any; value: any}>; children: React.ReactElement}) {
   let previous = props.children;
   for (let i = props.providers.length - 1; i >= 0; i--) {
      previous = React.createElement(props.providers[i].provider, {value: props.providers[i].value}, previous);
   }
   return previous;
}

Kemudian di komponen penyediaan tingkat atas saya:

public render() {
      return (
         <MultiProvider
            providers={[
               provider(Context1.Provider, this.context1),
               provider(Context2.Provider, this.context2),
               provider(Context3.Provider, this.context3),
               provider(Context4.Provider, this.context4),
               provider(Context5.Provider, this.context5),
            ]}
         ><AppComponents />
      </MultiProvider>
}

@gaul

Saya tidak melihat sesuatu yang buruk dengan menempatkan beberapa lapisan JSX di suatu tempat di bagian atas aplikasi.

Saya memiliki ~ 15 ketergantungan yang ingin saya suntikkan dengan cara itu, dan memiliki 15 tingkat lekukan tidak terlihat cantik bagi saya :)

@ 0xorial Anda tidak benar-benar perlu memiliki komponen untuk itu, karena <>> hanyalah sebuah pemanggilan fungsi React.createElement. Jadi Anda bisa menyederhanakannya menjadi fungsi tulis seperti:

const compose = (contexts, children) =>
  contexts.reduce((acc, [Context, value]) => {
    return <Context.Provider value={value}>{acc}</Context.Provider>;
  }, children);

dan gunakan sebagai:

import Context1 from './context1';
import Context2 from './context2';
import Context3 from './context3';
...
import Context15 from './context15';

const MyComponent = (props) => {
  // const value1..15 = ... get the values from somewhere ;

  return compose(
    [
      [Context1, value1],
      [Context2, value2],
      [Context3, value3],
      ...
      [Context15, value15],
    ],
    <SomeSubComponent/>
  );
}

Saya pernah menulis perpustakaan yang menangani kasus ini: https://github.com/disjukr/join-react-context

react11

ini pasti sesuatu yang terjadi di aplikasi sepanjang waktu. useContext bagus untuk _mengonsumsi_ data kontekstual dalam sebuah komponen, tetapi tidak terlalu bagus ketika Anda perlu _provide_ konteks dalam aplikasi dengan banyak penyedia.

Berikut adalah alternatif penutupan dari solusi @alesmenzelsocialbakers :

const composeProviders = (...Providers) => (Child) => (props) => (
  Providers.reduce((acc, Provider) => (
    <Provider>
      {acc}
    </Provider>
  ), <Child {...props} />)
)

const WrappedApp = composeProviders(
  ProgressProvider,
  IntentsProvider,
  EntitiesProvider,
  MessagesProvider
)(App)

ReactDOM.render(<WrappedApp />, document.getElementById('root'));

Kelemahannya adalah Anda harus menulis setiap komponen Penyedia tertentu.
Contoh:

export const ProgressProvider = ({ children }) => {
  const [progress, setProgress] = useState(0)

  return (
    <ProgressContext.Provider value={{ progress, setProgress }}>
      {children}
    </ProgressContext.Provider>
  )
}

Saya telah membuat perpustakaan manajemen negara bagian yang lebih baik dalam komposisi layanan. Berikut adalah demo menghindari neraka penyedia . Jangan ragu untuk mencobanya atau membaca sumbernya (100 baris kode)!

Ini memperkenalkan objek "cakupan" untuk mengumpulkan penyedia konteks, sehingga:

  • Layanan dapat diisolasi atau disusun, bergantung pada apakah layanan tersebut berada dalam cakupan yang sama.

    • Layanan dapat menggunakan layanan sebelumnya dalam cakupan yang sama, meskipun layanan tersebut berada dalam komponen yang sama.

  • Semua penyedia yang dikumpulkan oleh suatu cakupan dapat disediakan di onece, menghindari neraka penyedia.

Saya mengambil celah dalam hal ini juga. Ini sepertinya berfungsi dengan baik:

const composeWrappers = (
  wrappers: React.FunctionComponent[]
): React.FunctionComponent => {
  return wrappers.reduce((Acc, Current): React.FunctionComponent => {
    return props => <Current><Acc {...props} /></Current>
  });
}

Penggunaannya adalah:

const SuperProvider = composeWrappers([
    props => <IntlProvider locale={locale} messages={messages} children={props.children} />,
    props => <ApolloProvider client={client}>{props.children}</ApolloProvider>,
    props => <FooContext.Provider value={foo}>{props.children}</FooContext.Provider>,
    props => <BarContext.Provider value={bar}>{props.children}</BarContext.Provider>,
    props => <BazContext.Provider value={baz}>{props.children}</BazContext.Provider>,
  ]);
  return (
    <SuperProvider>
      <MainComponent />
    </SuperProvider>
  );

Saya juga menerbitkan helper ini sebagai perpustakaan npm react-compose-wrappers

Berikut ini menunjukkan bagaimana saya meneruskan pengguna yang diautentikasi ke komponen yang membutuhkannya.

Saya memutuskan untuk membuat satu status untuk aplikasi saya. Di file State.js saya, saya mengatur status awal, konteks, peredam, penyedia, dan hook.

import React, { createContext, useContext, useReducer } from 'react';

const INITIAL_STATE = {}

const Context = createContext();

const reducer = (state, action) => 
  action 
    ? ({ ...state, [action.type]: action[action.type] }) 
    : state;

export const Provider = ({ children }) => (
  <Context.Provider value={ useReducer(reducer, INITIAL_STATE) }>
    { children }
  </Context.Provider>
);

const State = () => useContext(Context);

export default State;

Lalu di file index.js saya membungkus aplikasi saya di penyedia.

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './State';
import App from './App';

ReactDOM.render(
  <React.StrictMode>
    <Provider>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root'),
);

Untuk mengkonsumsi state dalam sebuah komponen saya bisa menggunakan hook. Saya juga dapat menggunakan pengiriman untuk memperbarui status. Misalnya jika saya ingin mendapatkan atau menetapkan pengguna.

import React, {useEffect} from 'react';
import State from './State'

const ExampleComponent = () => {
  const [{ user }, dispatch] = State(); 

  useEffect(() => {
    const getUser = async () => {
      const data = await fetch('http://example.com/user.json');  // However you get your data
      dispatch({ type: 'user', user: data });
    }
    getUser();
  }, [dispatch]);

  // Don't render anything until user is retrieved
  // The user is undefined since I passed an empty object as my initial state
  if(user === undefined) return null; 

  return(
    <p>{user.name}</p>
  );
}

export default ExampleComponent;

Saya pikir cara ini memberi saya kebebasan untuk membangun keadaan seperti yang saya butuhkan tanpa menambahkan banyak konteks tambahan dan membantu saya menghindari sarang penyedia yang dalam.

API Hooks yang akan datang menyediakan cara berbeda untuk menggunakan konteks.

https://reactjs.org/docs/hooks-reference.html#usecontext

Bagaimana cara menggunakan ini di komponen kelas?

API Hooks yang akan datang menyediakan cara berbeda untuk menggunakan konteks.
https://reactjs.org/docs/hooks-reference.html#usecontext

Bagaimana cara menggunakan ini di komponen kelas?

Bukankah hook digunakan untuk memanfaatkan berbagai fitur React tanpa kelas menulis?
Artinya, semua yang dilakukan berbagai hook sudah ada di kelas. Jika Anda berbicara tentang sintaks yang mudah digunakan dan penggunaan api, maka react berpindah dari kelas ke komponen fungsional, jadi selamat datang di fungsi dan kait)

Saya membuat paket untuk memecahkan masalah dengan menyediakan API serupa dengan vue3

https://github.com/TotooriaHyperion/react-multi-provide

  • memecahkan masalah yang konteks react membutuhkan pohon tampilan ekstra.
  • juga menjaga reaktivitas dari apa yang disuntikkan
  • fraktal antar penyedia
  • gunakan WeakMap untuk menyimpan dependensi
  • gunakan simbol yang dibungkus objek untuk mendapatkan dukungan skrip jenis, injeksi dependensi & pengalaman debug yang lebih baik

memperhatikan:

  • Saya tidak menyarankan Anda untuk sangat bergantung pada reaktivitas konteks , karena lebih baik memberikan akses ke data daripada data itu sendiri. Apa yang dikatakan @gaearon tentang jangan berlangganan terlalu banyak konteks adalah benar. Namun ia tidak menyebutkan bahwa menyelesaikan langganan data dengan mengandalkan reaktivitas konteks itu merupakan pola yang salah . Jadi, hal yang benar untuk dilakukan adalah tidak menggunakan banyak konteks tetapi memberikan dependensi Anda dalam satu konteks . Dan sementara itu, pertahankan konteks Anda agar tetap stabil .
  • Jadi, jika kita menginginkan API yang lebih baik, kita perlu menanganinya sendiri, dan ingat untuk membuat API fraktal. Untuk itulah paket saya.

Outer.tsx

import React, { useMemo } from "react";
import { Providers, useCreateContexts, useProvide } from "../..";
import { createService, ServiceA } from "./service";

export const Outer: React.FC = ({ children }) => {
  const contexts = useCreateContexts();
  const service = useMemo(createService, []);
  useProvide(contexts, ServiceA.id, service);
  return <Providers contexts={contexts}>{children}</Providers>;
};

Inner2.tsx

import React from "react";
import { useContexts, useReplaySubject } from "../..";
import { ServiceA } from "./service";

export const Inner2: React.FC = () => {
  const [
    {
      state$,
      actions: { inc, dec },
    },
  ] = useContexts([ServiceA.id]);
  const count = useReplaySubject(state$);
  return (
    <>
      <p>{count}</p>
      <div>
        <button onClick={inc}>Increment</button>
        <button onClick={dec}>Decrement</button>
      </div>
    </>
  );
};

inilah cara saya melakukannya:

interface Composable {
    (node: React.ReactNode): React.ReactElement
}

const composable1: Composable = (node)=>{
      return <someContext.Provider>{node}</someContext.Provider>
}

function Comp({children}:{children?:React.ReactNode}){
       return pipe(
             composabl1, composabl2, composable3
       )(children)
}

Anda dapat menemukan fungsi pipe di banyak pustaka populer seperti rxjs, ada juga beberapa proposal tingkat bahasa untuk operasi mirip pipeline ini. Tidak perlu 'menyelesaikan' dengan menggunakan lib lain.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

sophiebits picture sophiebits  ·  107Komentar

AdamKyle picture AdamKyle  ·  148Komentar

acdlite picture acdlite  ·  83Komentar

gabegreenberg picture gabegreenberg  ·  264Komentar

gaearon picture gaearon  ·  133Komentar