React: كيف ستحل الاستجابة السياقات المتداخلة؟

تم إنشاؤها على ١٨ يناير ٢٠١٩  ·  38تعليقات  ·  مصدر: facebook/react

لدي الكثير من السياقات ويجب أن أكتب بهذه الطريقة ، إنها قبيحة للغاية! يعيق عملي الآن. مثل هذا التصميم يجعلها غير قابلة للاستخدام تقريبًا.

<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

التعليق الأكثر فائدة

@ 0xorial ، فأنت لا تحتاج حقًا إلى مكون لذلك ، بعد كل شيء <>> هي مجرد وظيفة استدعاء React.createElement. لذلك يمكنك تبسيطها إلى وظيفة إنشاء مثل:

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

واستخدامها على النحو التالي:

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

ال 38 كومينتر

تقدم Hooks API القادمة طريقة مختلفة لاستهلاك السياقات.

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

شكرا لك. لكن ماذا عن المزود؟

انا سوف اكون صادق. إذا كنت تواجه هذا النوع من التنفيذ ، فإن تصميمك الهندسي يبدو سيئًا وربما لا يجب عليك استخدام سياق React.

كلا ، أنا أصمم حاوية متجر جديدة. يحتاج إلى العمل مع السياق في رد الفعل.
https://github.com/rabbitooops/rako

ما علاقة هذه المكتبة بخمس طبقات عميقة من المزودين / المستهلكين؟

لأنه يدعم حقن مخازن متعددة للتفاعل بدلاً من حل متجر واحد مثل Redux.

في هذه الحالة ، يكون التعامل مع السياق الآن على مستخدمي المكتبة ، وبدرجة أقل على المكتبة. إن كيفية استخدامهم للميزات أمر متروك لهم ، وإذا كانوا يريدون جميع مقدمي الخدمة في مكان واحد (وهو ما يتعارض مع الغرض من وجود متاجر متعددة) ، فهذا اختيارهم. من الناحية المثالية ، سيتم تنفيذ حل متجر متعدد في تقسيمات مختلفة في التطبيق ، لذا فإن السياقات المتداخلة مثل هذه أكثر ندرة.

2 سنتي على الأقل.

لذا فإن واجهة برمجة تطبيقات السياق ملائمة فقط لحل المتجر الفردي مثل redux.

على الاطلاق. لكن من الصعب أيضًا مناقشة مشكلتك دون تقديم مثال أكثر واقعية. الرجاء إنشاء واحد؟

تخيل أن هناك ثلاثة متاجر موضوع ، مستخدم وعداد.

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

    /* ... */
  }
}

ماذا ستكون لغتك المثالية؟

استخدم Context.write ، وادعم استهلاك سياقات متعددة دون تداخل.

يتم دعم استهلاك سياقات متعددة بدون تداخل بالفعل. (مع خطاف.)

يحتوي Context.write على RFC مفتوح له. لا نعرف ما إذا كانت ستنجح لأنها تثير بعض الأسئلة المعقدة للغاية. ولكن أثناء فتح RFC ، لست متأكدًا مما هو قابل للتنفيذ في هذه المشكلة. هل لديك شيء تضيفه يتجاوز ما هو موجود بالفعل في دافع RFC؟

اريد ان اسال سؤال لماذا لا تدعم الاستجابة باستهلاك سياقات متعددة في الفصل؟ يبدو أن واجهة برمجة تطبيقات hooks بها الكثير من المشكلات التي يتعين حلها وهي غير مستقرة جدًا في الوقت الحالي

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

سأقوم بتنفيذ StoreProviders والذي يمكنه تضمين سياقات متعددة تلقائيًا.

const StoreProviders = constructStoreProviders(...storeContexts)

<StoreProviders>
  <Child />
</StoreProviders>

شكرا على مساعدتك دان! :)

rabbitooops ما هي بالضبط مشكلات الخطافات التي لديك؟ أستخدم الخطافات في الإنتاج وهي تعمل جيدًا لفريقي.

وماذا عن هذا؟ من الآمن استخدام الخطاف الآن. تضمين التغريدة

// 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 ماذا عن استخدام متجر واحد ورمز كمفاتيح لتقليد طبقات متعددة من المتجر؟

data Store = Leaf Object | C Store Store

أو بطريقة غير كاملة في جافا سكريبت:

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 مختلف: App.useProvider

zhujinxuan أنا آسف ، لا أستطيع الحصول عليك.

zhujinxuan يمكنك استخدام غير مذكور ، مثال:

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

يبدو أن واجهة برمجة تطبيقات hooks بها الكثير من المشكلات التي يتعين حلها وهي غير مستقرة جدًا في الوقت الحالي

نحن نستعد لإصداره في غضون أسبوع أو أسبوعين - لست متأكدًا من سبب استنتاجك لذلك. ستكون جاهزة قريبًا على الرغم من أنك إذا كنت تريد أن تكون آمنًا ، فيرجى الانتظار حتى إصدار مستقر.

وماذا عن هذا؟

لا يُسمح عمومًا باستدعاء الخطافات في حلقة (كما تفعل في forEach ). من السهل التسبب في حدوث مشكلات بهذه الطريقة.

useStoreProviders

كلا useProvider و useShouldComponentUpdate يمثلان مشكلة مثل الخطافات (وهذا هو سبب عدم وجودهما في React). انظر إجابتي في https://github.com/facebook/react/issues/14534#issuecomment -455411307.


بشكل عام ، أجد صعوبة في فهم الغرض من هذه المشكلة.

يتم حل استهلاك سياقات متعددة عن طريق ربط useContext . لا نوصي "بأتمتة" المصفوفات بطريقة ما لأن هذا يجعل من السهل جدًا كتابة مكونات تشترك في الكثير من السياق وإعادة العرض كثيرًا. يجب أن يكون لديك فكرة واضحة عن السياقات التي يستمع إليها المكون ، وهو ما يمنحك useContext API. إذا كان لا بد من ذلك ، يمكنك كتابة useMyContexts() Hook الذي يستخدم سياقات محددة بشكل صريح. أنا فقط لا أوصي بجعلها ديناميكية كما فعلت لأنه إذا تغير طول الصفيف ، يمكن أن ينكسر.

يمكن اعتبار وضع العديد من مقدمي الخدمة على أنه "نموذجي" وقد يكون لدينا في النهاية حل لذلك. لكنني أيضًا لا أفهم لماذا تعتبرها مشكلة كبيرة. الأمثلة في هذا الموضوع ليست واقعية بما يكفي لشرح المشكلة لي. لا أرى أي شيء سيئًا في تداخل عدة طبقات من JSX في مكان ما في الجزء العلوي من التطبيق. جميلة بالتأكيد لديك أكثر عمقا div تعشش في معظم مكوناتها والتي لا تؤذي كثيرا.

سأختتم هذا لأنني أجبت بالفعل على هذه النقاط ، والمناقشة تدور في دوائر. إذا كان هناك شيء مفقود أعلمني.

الوقت الإضافي : useRender أو شيء ما لتحكم أكثر في العرض؟ على سبيل المثال:

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

الوسيط الثاني له نفس دور الخطاف useEffect .

useMemo صديقك.

راجع المقتطف الثاني في https://reactjs.org/docs/hooks-faq.html#how -to-memoize-calculations.

انتهى بي الأمر برمز كهذا:

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

ثم في مكون التزويد عالي المستوى الخاص بي:

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

تضمين التغريدة

لا أرى أي شيء سيئًا في تداخل عدة طبقات من JSX في مكان ما في الجزء العلوي من التطبيق.

لدي 15 تبعيات أريد أن أكون قابلة للحقن بهذه الطريقة ، ولا يبدو لي أن امتلاك 15 مستوى من المسافة البادئة أمر جميل :)

@ 0xorial ، فأنت لا تحتاج حقًا إلى مكون لذلك ، بعد كل شيء <>> هي مجرد وظيفة استدعاء React.createElement. لذلك يمكنك تبسيطها إلى وظيفة إنشاء مثل:

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

واستخدامها على النحو التالي:

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

لقد كتبت مكتبة في الماضي تتعامل مع هذه الحالة: https://github.com/disjukr/join-react-context

react11

هذا بالتأكيد شيء يحدث في التطبيقات طوال الوقت. useContext أمرًا رائعًا لاستهلاك البيانات السياقية في أحد المكونات ، ولكنه ليس رائعًا عندما تحتاج إلى _توفير_ سياق في تطبيق به عدة موفرين.

إليك بديل إغلاق لحل 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'));

الجانب السلبي هو أنه عليك كتابة كل مكون معين من مكونات المزود.
مثال:

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

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

لقد قمت بإنشاء مكتبة إدارة حكومية أفضل في تكوين الخدمة. هنا عرض توضيحي لتجنب الجحيم المقدم . لا تتردد في تجربته أو قراءة مصدره (100 سطر من التعليمات البرمجية)!

يقدم كائن "نطاق" لجمع موفر السياق ، بحيث:

  • يمكن عزل الخدمات أو تكوينها ، اعتمادًا على ما إذا كانت في نفس النطاق.

    • يمكن أن تستهلك الخدمات الخدمات السابقة في نفس النطاق ، على الرغم من أنها في نفس المكون.

  • يمكن توفير جميع مقدمي الخدمة الذين تم جمعهم بواسطة النطاق في onece ، مع تجنب جحيم المزود.

أخذت صدعًا في هذا أيضًا. تبدو أنها تعمل جيدا:

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

الاستخدام:

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

لقد نشرت أيضًا هذا المساعد كمكتبة npm react-compose-wrappers

يوضح ما يلي كيف أمرر المستخدم المصادق إلى المكونات التي تحتاج إليه.

قررت إنشاء دولة واحدة لطلبي. في ملف State.js الخاص بي ، قمت بإعداد حالتي الأولية ، والسياق ، والمخفض ، والموفر ، والخطاف.

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;

ثم في ملف index.js الخاص بي ، قمت بلف تطبيقي في الموفر.

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'),
);

لاستهلاك الحالة في مكون يمكنني استخدام الخطاف. يمكنني أيضًا استخدام الإرسال لتحديث الحالة. على سبيل المثال إذا كنت أرغب في الحصول على مستخدم أو تعيينه.

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;

أعتقد أن هذه الطريقة تمنحني الحرية في بناء الدولة التي أحتاجها دون إضافة الكثير من السياقات الإضافية وتساعدني على تجنب عش عميق من مقدمي الخدمات.

تقدم Hooks API القادمة طريقة مختلفة لاستهلاك السياقات.

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

كيف يمكنني استخدام هذا في مكون الفصل؟

تقدم Hooks API القادمة طريقة مختلفة لاستهلاك السياقات.
https://reactjs.org/docs/hooks-reference.html#usecontext

كيف يمكنني استخدام هذا في مكون الفصل؟

ألا تُستخدم الخطافات للاستفادة من ميزات React المختلفة دون كتابة فئات؟
حسنًا ، هذا هو ، كل ما تفعله الخطافات المختلفة موجود بالفعل في الفصول الدراسية. إذا كنت تتحدث عن البنية الملائمة والاستخدام API ، فإن التفاعل ينتقل بعيدًا عن الفئات إلى المكونات الوظيفية ، لذا مرحبًا بك في الوظائف والخطافات)

لقد أنشأت حزمة لحل المشكلة من خلال توفير واجهة برمجة تطبيقات مماثلة مع vue3

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

  • حل المشكلة التي يتطلب سياق التفاعل شجرة عرض إضافية.
  • يحافظ أيضًا على تفاعل ما تم حقنه
  • كسورية بين مقدمي
  • استخدم WeakMap لتخزين التبعيات
  • استخدم رمز التفاف الكائن لتحقيق دعم أفضل للنص المطبوع وحقن التبعية وتجربة التصحيح

تنويه:

  • لا أنصحك بالاعتماد بشكل كبير على تفاعل السياق ، لأنه من الأفضل توفير الوصول إلى البيانات بدلاً من البيانات نفسها. ما قالهgaearon عن عدم الاشتراك في العديد من السياقات كان صحيحًا. لكنه لم يذكر أنه نمط خاطئ لحل اشتراك البيانات من خلال الاعتماد على تفاعل السياق . لذا فإن الشيء الصحيح الذي يجب فعله هو عدم استخدام سياقات متعددة ولكن تقديم تبعياتك في سياق واحد . وفي الوقت نفسه ، حافظ على سياقاتك مستقرة قدر الإمكان .
  • وبالتالي ، إذا أردنا واجهة برمجة تطبيقات أفضل ، فنحن بحاجة إلى التعامل معها بأنفسنا ، ونضع في اعتبارنا لجعل API كسورية. هذا هو الغرض من مجموعتي.

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

إليك كيف أفعل ذلك:

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

يمكنك العثور على الوظيفة pipe في العديد من المكتبات الشائعة مثل rxjs ، وهناك أيضًا العديد من المقترحات على مستوى اللغة لهذه العملية التي تشبه خط الأنابيب. ليست هناك حاجة لحلها باستخدام lib آخر.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات