Как манипулировать контекстом - прикрепить функцию к контексту или обернуть диспетчерскую в ловушку? - PullRequest
9 голосов
/ 22 апреля 2019

Мне интересно, какова рекомендуемая лучшая практика для манипулирования и раскрытия нового контекста React.

Кажется, что самый простой способ манипулировать состоянием контекста - просто прикрепить функцию к контексту, который либо отправляет (usereducer) или setstate (useState) для изменения своего внутреннего значения после вызова.

<code>export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);

  return (
    <Context.Provider
      value={{
        todos: state.todos,
        fetchTodos: async id => {
          const todos = await getTodos(id);
          console.log(id);
          dispatch({ type: "SET_TODOS", payload: todos });
        }
      }}
    >
      {children}
    </Context.Provider>
  );
};

export const Todos = id => {
  const { todos, fetchTodos } = useContext(Context);
  useEffect(() => {
    if (fetchTodos) fetchTodos(id);
  }, [fetchTodos]);
  return (
    <div>
      <pre>{JSON.stringify(todos)}
);};

Мне, однако, сказали, что подвергать и использовать объект контекста реакции напрямую, вероятно, не очень хорошая идея, и мне сказали вместо этого обернуть его внутри хука.

<code>export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);

  return (
    <Context.Provider
      value={{
        dispatch,
        state
      }}
    >
      {children}
    </Context.Provider>
  );
};

const useTodos = () => {
  const { state, dispatch } = useContext(Context);
  const [actionCreators, setActionCreators] = useState(null);

  useEffect(() => {
    setActionCreators({
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    });
  }, []);

  return {
    ...state,
    ...actionCreators
  };
};

export const Todos = ({ id }) => {
  const { todos, fetchTodos } = useTodos();
  useEffect(() => {
    if (fetchTodos && id) fetchTodos(id);
  }, [fetchTodos]);

  return (
    <div>
      <pre>{JSON.stringify(todos)}
);};

Я сделал примеры выполнения кода для обоих вариантов здесь: https://codesandbox.io/s/mzxrjz0v78?fontsize=14

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

Ответы [ 2 ]

6 голосов
/ 26 апреля 2019

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

Если у вас есть несколько компонентов в приложении, где вы хотите использовать контекст TodoProvider, или у вас есть несколько контекстов в вашем приложении, вы немного упростите его с помощью пользовательского хука

Также еще одна вещь, которую вы должны учитывать при использовании контекста, это то, что вы не должны создавать новый объект при каждом рендеринге, в противном случае все компоненты, которые используют context, будут перерисовываться, даже если ничего не изменилось бы. Для этого вы можете использовать useMemo hook

<code>const Context = React.createContext<{ todos: any; fetchTodos: any }>(undefined);

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);
  const context = useMemo(() => {
    return {
      todos: state.todos,
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    };
  }, [state.todos, getTodos]);
  return <Context.Provider value={context}>{children}</Context.Provider>;
};

const getTodos = async id => {
  console.log(id);
  const response = await fetch(
    "https://jsonplaceholder.typicode.com/todos/" + id
  );
  return await response.json();
};
export const useTodos = () => {
  const todoContext = useContext(Context);
  return todoContext;
};
export const Todos = ({ id }) => {
  const { todos, fetchTodos } = useTodos();
  useEffect(() => {
    if (fetchTodos) fetchTodos(id);
  }, [id]);
  return (
    <div>
      <pre>{JSON.stringify(todos)}
); };

Рабочая демоверсия

EDIT:

Поскольку getTodos - это просто функция, которая не может измениться, делает ли она смысл использовать это как аргумент обновления в useMemo?

Имеет смысл передать getTodos в массив зависимостей в useMemo, если метод getTodos изменяется и вызывается внутри функционального компонента. Часто вы запоминаете метод, используя useCallback, чтобы он не создавался при каждом рендеринге, а только в том случае, если какая-либо его зависимость от включаемой области изменяется, чтобы обновить зависимость в его лексической области. Теперь в таком случае вам нужно будет передать его в качестве параметра в массив зависимостей.

Однако в вашем случае вы можете его опустить.

Также, как бы вы справились с начальным эффектом. Скажи, если ты должен был позвонить `getTodos´ в использовании Перехватить эффект при монтировании провайдера? Не могли бы вы запомнить что за звонок?

Вы бы просто имели эффект внутри провайдера, который вызывается при первоначальном монтировании

export const TodosProvider: React.FC<any> = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, null, init);
  const context = useMemo(() => {
    return {
      todos: state.todos,
      fetchTodos: async id => {
        const todos = await getTodos(id);
        console.log(id);
        dispatch({ type: "SET_TODOS", payload: todos });
      }
    };
  }, [state.todos]);
  useEffect(() => {
      getTodos();
  }, [])
  return <Context.Provider value={context}>{children}</Context.Provider>;
};
1 голос
/ 24 апреля 2019

Не думаю, что есть официальный ответ, поэтому давайте попробуем использовать здравый смысл здесь. Я считаю вполне подходящим использование useContext напрямую, я не знаю, кто сказал вам не делать этого, возможно, ОН / ОНА должен был указывать на официальные документы. Зачем команде React создать этот хук, если он не должен был использоваться? :)

Однако я могу понять, что стараюсь избегать создания огромного объекта как value в Context.Provider, который смешивает состояние с функциями, которые им манипулируют, возможно, с асинхронными эффектами, такими как ваш пример.

Однако в своем рефакторе вы ввели очень странный и абсолютно ненужный useState для создателя действий, который вы просто определили встроенным при первом подходе. Мне кажется, что вы искали useCallback вместо этого. Итак, почему бы вам не смешать и то и другое?

  const useTodos = () => {
    const { state, dispatch } = useContext(Context);
    const fetchTodos = useCallback(async id => {
      const todos = await getTodos(id)
      dispatch({ type: 'SAVE_TODOS', payload: todos })
    }, [dispatch])

    return {
      ...state,
      fetchTodos
    };
}

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

<code>export const Todos = id => {
  const { todos, fetchTodos } = useContext(Context);
  useEffect(() => {
    fetchTodos()
  }, []);

  return (
    <div>
      <pre>{JSON.stringify(todos)}
); };

Наконец, если вам не нужно использовать эту комбинацию todos + fetchTodos из нескольких компонентов в дереве, начиная с Todos, которые вы явно не указали в своем вопросе, я думаю, что использование Context усложняет ситуацию, когда они не нужны. Удалите лишний слой косвенности и позвоните useReducer прямо в ваш useTodos.

Возможно, это не тот случай, но я нахожу, что люди смешивают много вещей в своей голове и превращают что-то простое в нечто сложное (например, Redux = Context + useReducer).

Надеюсь, это поможет!

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