Можно ли использовать то же самоена несколько компонентов? - PullRequest
0 голосов
/ 20 октября 2019

Я знаю, что могу обернуть HOC своим <Context.Provider> и использовать его во всех дочерних компонентах.

Я хотел бы использовать контекст в двух отдельных компонентах, но они где-то глубоко вложены и являются ближайшим к ним родителем. находится где-то в корне приложения. Я не хочу предоставлять контекст (почти) всем компонентам, поэтому мне было интересно, можно ли обернуть только эти два компонента?

Я пытался это сделать, но только первый компонент получает контекст.

Структура приложения выглядит следующим образом:

<App>
    <A1>
        <A2>
            <MyContext.Provider>
                <Consumer1/>
            </MyContext.Provider>
        </A2>
    </A1>
    <B1>
        <B2>
            <MyContext.Provider>
                <Consumer2/>
            </MyContext.Provider>
        </B2>
    </B1>
</App>

РЕДАКТИРОВАТЬ: я был не прав, думая, что перенос корневого компонента заставит повторно визуализировать все дочерние компоненты при изменении контекста. Только потребители будут повторять, поэтому очень хорошо обернуть корневой компонент.

Ответы [ 2 ]

1 голос
/ 20 октября 2019

Если вы хотите иметь одно значение, которое совместно используется несколькими частями приложения, то в некоторой форме вам нужно будет переместить это значение до общего компонента-предка из тех, которые должны использовать это значение. Как вы упомянули в комментариях, ваша проблема связана с производительностью и попыткой не перерисовывать все. Наличие двух провайдеров на самом деле не помогает в этом, потому что все еще должен быть какой-то компонент, обеспечивающий одинаковую ценность для обоих провайдеров. Таким образом, этот компонент должен будет стать общим предком двух провайдеров.

Вместо этого вы можете использовать shouldComponentUpdate (для компонентов класса) или React.memo (для функциональных компонентов), чтобы остановить работу процесса рендеринга. его путь вниз по дереву компонентов. Глубокие потомки, которые используют Context.Consumer, все равно будут перерисовываться, поэтому вы можете пропустить средние части вашего дерева. Вот пример (обратите внимание на использование React.memo на промежуточном компоненте):

const Context = React.createContext(undefined);

const useCountRenders = (name) => {
  const count = React.useRef(0);
  React.useEffect(() => {
    count.current++;
    console.log(name, count.current);
  });
}

const App = () => {
  const [val, setVal] = React.useState(1);
  useCountRenders('App');
  
  React.useEffect(() => {
    setTimeout(() => {
      console.log('updating app');
      setVal(val => val + 1)
    }, 1000);
  }, [])
  
  return (
   <Context.Provider value={val}>
     <IntermediateComponent />
   </Context.Provider>
  );
}

const IntermediateComponent = React.memo((props) => {
  useCountRenders('intermediate');
  return (
    <div>
      <Consumer name="first consumer"/>
      <UnrelatedComponent/>
      <Consumer name="second consumer"/>
    </div>
  );
})

const Consumer = (props) => {
  useCountRenders(props.name);
  return (
    <Context.Consumer>
      {val => {
        console.log('running consumer child', props.name);
        return <div>consuming {val}</div>
      }}
    </Context.Consumer>
  )
}

const UnrelatedComponent = (props) => {
  useCountRenders('unrelated');
  return props.children || null;
}


ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.development.js"></script>
<div id="root"></div>

Когда вы запустите приведенный выше код, проверьте журналы, чтобы увидеть, какие компоненты перерисовываются. На первом проходе все рендерится, но затем, через секунду, когда состояние приложения меняется, только приложение перерисовывается. IntermediateComponent, UnrelatedComponent и даже Consumer не выполняют повторную визуализацию. Функция внутри Context.Consumer перезапускается, и любая вещь, возвращаемая этой функцией (в данном случае просто div), будет перерисовываться.

0 голосов
/ 20 октября 2019

По требованию OP это решение использует в основном перехватчики, но useReducer не может обеспечить совместное использование состояний у отдельных провайдеров (насколько я пытался).

Не требуется, чтобы один провайдер находился в корнеприложение и другой редуктор могут использоваться для каждого провайдера.

В нем используется диспетчер статического состояния, но это деталь реализации, чтобы разделить состояние между несколькими компонентами в разных провайдерах контекста, для одного потребуется нечто вроде общей ссылкив состояние, способ его изменения и способ уведомления об этих изменениях.

При использовании фрагмента первая кнопка отображает общее состояние и приращение foo при нажатии, вторая кнопка показывает тот же общийсостояние и увеличивается bar при нажатии:

// The context
const MyContext = React.createContext();

// the shared static state
class ProviderState {
  static items = [];
  static register(item) {
    ProviderState.items.push(item);
  }
  static unregister(item) {
    const idx = ProviderState.items.indexOf(item);
    if (idx !== -1) {
      ProviderState.items.splice(idx, 1);
    }
  }
  static notify(newState) {
    ProviderState.state = newState;
    ProviderState.items.forEach(item => item.setState(newState));
  }

  static state = { foo: 0, bar: 0 };
}


// the state provider which registers to (listens to) the shared state
const Provider = ({ reducer, children }) => {
  const [state, setState] = React.useState(ProviderState.state);
  React.useEffect(
    () => {
      const entry = { reducer, setState };
      ProviderState.register(entry);
      return () => {
        ProviderState.unregister(entry);
      };
    },
    []
  );
  return (
      <MyContext.Provider
        value={{
          state,
          dispatch: action => {
            const newState = reducer(ProviderState.state, action);
            if (newState !== ProviderState.state) {
              ProviderState.notify(newState);
            }
          }
        }}
      >
        {children}
      </MyContext.Provider>
  );
}

// several consumers
const Consumer1 = () => {
  const { state, dispatch } = React.useContext(MyContext);
  // console.log('render1');
  return <button onClick={() => dispatch({ type: 'inc_foo' })}>foo {state.foo} bar {state.bar}!</button>;
};

const Consumer2 = () => {
  const { state, dispatch } = React.useContext(MyContext);
  // console.log('render2');
  return <button onClick={() => dispatch({ type: 'inc_bar' })}>bar {state.bar} foo {state.foo}!</button>;
};

const reducer = (state, action) => {
  console.log('reducing action:', action);
  switch(action.type) {
    case 'inc_foo':
      return {
        ...state,
        foo: state.foo + 1,
      };  
    case 'inc_bar':
      return {
        ...state,
        bar: state.bar + 1,
      };
    default: 
      return state;
  }

}
// here the providers are used on the same level but any depth would work
class App extends React.Component {
  
  render() {
    console.log('render app');
    return (
      <div>
        <Provider reducer={reducer}>
          <Consumer1 />
        </Provider>
        <Provider reducer={reducer}>
          <Consumer2 />
        </Provider>
        <h2>I&apos;m not rerendering my children</h2>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

В итоге мы создали нечто похожее на redux со статическим состоянием, которое используется несколькими ансамблями провайдеров / потребителей.

Он будет работать одинаково, независимо от того, где и насколько глубоко провайдеры и потребители не будут полагаться на длинные вложенные цепочки обновлений.

Обратите внимание, что в соответствии с запросом OP здесь нет необходимости в провайдерекорень приложения и, следовательно, контекст предоставляется не каждому компоненту, только выбранное вами подмножество.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...