React Hooks: setState, когда это действительно вступает в силу? - PullRequest
1 голос
/ 25 мая 2020

Итак, я пытаюсь понять React Hooks и как их использовать. Я могу выполнить выборку внутри компонента следующим образом:

var [pages,setPages] = useState();
var [page,setPage] = useState();

 async function fetchData() {

  await fetch(`https://myBackend/pages`)
  .then(response => response.json())
  .then(response => {
      setPages(response); 
      console.log(pages[0].title);    
  })
 .then(()=>{

  // AT THIS STAGE, pages IS UNDEFINED

  setPage(pages.filter(singlepage=> {
        return singlepage.url == props.match.params.url;
    }))
 }
  .catch(err => setErrors(err));
 }

useEffect(() => {
  fetchData();
  console.log(pages[0].title);   
  return () => {
      console.log('unmounting...');
  }
  },[]);

Я вызываю свой сервер, чтобы получить все страницы, и это работает нормально, как если бы я закодировал pages[0].title, который он будет отображать. Но когда я пытаюсь получить доступ к определенным значениям c в pages внутри хука useEffect, чтобы я назначил page, это дает мне ошибку pages undefined. Моя запись в консоль делает это очевидным.

Я хочу, чтобы page, а также pages были определены при «монтировании компонентов» перед загрузкой страницы. Итак, мой вопрос: когда setPages на самом деле устанавливает страницу? и могу ли я назначить как внутри useEffect, так и на основе друг друга?

Ответы [ 2 ]

2 голосов
/ 25 мая 2020

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

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

Что касается получения доступа к переменной страниц сразу после вызова setPages, это также не удастся, потому что React фактически выполняет повторный рендеринг асинхронно.

Даже если он выполнялся одновременно, есть вещь под названием closures, в которой javascript извлекает все переменные вокруг функции при ее создании, так что функция всегда имеет доступ к ним. React Hooks работают за счет использования этих замыканий, чтобы иметь доступ только к переменным, какими они были, когда компонент был визуализирован / повторно визуализирован. Причина этого в том, что при каждом повторном рендеринге все функции в вашем компоненте воссоздаются, что создает новое закрытие.

Итак, это означает, что вам нужно сохранить эти концепции помните, когда вы работаете с React.

Что касается вашего кода, решение для результатов, которое настроил Dlucidione, является одним из ваших лучших вариантов, помимо настройки отдельного useEffect для обновления page при изменении pages .

const history = useHistory(); //from react-router-dom
useEffect(() => {
  async function fetchData() {
    return await fetch(`https://myBackend/pages`)
      .then((response) => response.json())
      .then((response) => {
        setPages(response);
      })
      .catch((err) => setErrors(err));
  }
  fetchData();
  return () => {
    console.log('unmounting...');
  };
}, []);

useEffect(() => {
  const url = props.match.params.url;
  if (!pages || !url) return;
  // Using Array#find here because I assume based on the name, you want one page rather than an array
  const page = pages.find((singlepage) => {
    return singlepage.url === url;
  });
  if (!page) {
    // This will change the route if a page isn't found.
    history.push('/404');
  }
  setPage(page);
}, [pages, props.match.params.url, history]);

Redirect и history.push работают по-разному. Redirect - это декларативный метод навигации, а history.push - это программный c.

Пример использования history.push:

  if (!page) {
    // This will change the route if a page isn't found.
    history.push('/404');
  }

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

Пример использования Redirect без перенаправления на mount:

if(!pages && !page){
 return <Redirect to="/404"/>
}

Или в каком-то JSX:

<div>
  {!pages && !page && <Redirect to="/404"/>}
</div>

Redirect имеет для рендеринга, что означает, что его можно использовать только в операторе return компонента.

Как я понимаю: перенаправление основано на том, найдена ли отдельная страница в процессе монтирования, поэтому процесс перенаправления также должен быть в процессе монтирования, чтобы проверить его перед рендерингом чего-либо перенаправлять или нет.

Это правильно. Когда Redirect монтируется или обновляется, он будет перенаправлять, если условия верны. Если в перенаправлении есть свойство path или from (они являются псевдонимами друг друга), то это ограничивает работу перенаправления только при совпадении этого пути. Если путь не совпадает, перенаправление ничего не сделает, пока путь не будет изменен на соответствующий (или, конечно, он не отключится).

Под капотом , перенаправление просто вызывает history.push или history.replace в componentDidMount и componentDidUpdate (через компонент Lifecycle), так что принимайте это как хотите.

0 голосов
/ 25 мая 2020
    useEffect(() => {
      const fetchData = async () => {
    try {
      // Make a first request
      const result = await axios.get(`firstUrl`);
      setPages(result);
      // here you can use result or pages to do other operation
      setPage(result.filter(singlepage=> {
      return singlepage.url == props.match.params.url;
      or 
      setPage(pages.filter(singlepage=> {
      return singlepage.url == props.match.params.url;
}))
    } catch (e) {
      // Handle error here
    }
  };

  fetchData();
}, []);
...