Реагирующие хуки: как отслеживать изменения в объекте класса JS?

#reactjs #react-hooks #state

#reactjs #реагирующие хуки #состояние

Вопрос:

Я совсем новичок в React и не всегда понимаю, когда мне нужно использовать хуки, а когда они мне не нужны.

Я понимаю, что вы можете получить / установить состояние, используя

 const [myState, setMyState] = React.useState(myStateValue);
  

Итак. Мой компонент выполняет некоторые функции на основе URL-адреса :

 const playlist = new PlaylistObj();

React.useEffect(() => {
  playlist.loadUrl(props.url).then(function(){
    console.log("LOADED!");
  })
}, [props.url]);
  

Внутри моего класса PlaylistObj у меня есть асинхронная функция loadUrl (url), которая

  • устанавливает для свойства apiLoading списка воспроизведения значение true
  • получает содержимое
  • устанавливает для свойства apiLoading списка воспроизведения значение false

Теперь я хочу использовать это значение в своем компоненте React, чтобы я мог задавать его классы (я использую classnames) :

   <div
  className={classNames({
    'api-loading': playlist.apiLoading
  })}
  >
  

Но это не работает; класс не обновляется, даже если я получаю сообщение «ЗАГРУЖЕНО!» в консоли.
Кажется, что объект списка воспроизведения не «просматривается» React. Может быть, мне следует использовать здесь состояние реакции, но как?

Я тестировал

 const  = React.useState(new PlaylistObj());
React.useEffect(() => {
  //refresh playlist if its URL is updated
  playlist.loadUrl(props.playlistUrl).then(function(){
    console.log("LOADED!");
  })
}, [props.playlistUrl]);
  

И это, но мне это кажется все более и более нелогичным, и, ну, не работает.

 const  = React.useState(new PlaylistObj());
React.useEffect(() => {
  playlist.loadUrl(props.playlistUrl).then(function(){
    console.log("LOADED!");
    setPlaylist(playlist); //added this
  })
}, [props.playlistUrl]);
  

Я просто хочу, чтобы мой компонент был обновлен с помощью объекта списка воспроизведения. Как мне с этим справиться?

Я чувствую, что что-то упускаю.

Большое спасибо!

Ответ №1:

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

 const  = React.useState(new PlaylistObj());

React.useEffect(() => {
  playlist.loadUrl(props.playlistUrl).then(function(){
    setPlaylist(playlist); // <-- this playlist reference doesn't change
  })
}, [props.playlistUrl]);
  

Я думаю, вам следует ввести второе isLoading состояние для вашего компонента. Когда эффект срабатывает при обновлении URL-адреса, начните с установки loading true , а когда обещание будет выполнено, обновите его обратно на false.

 const  = React.useState(new PlaylistObj());
const [isloading, setIsLoading] = React.useState(false);

React.useEffect(() => {
  setIsLoading(true);
  playlist.loadUrl(props.playlistUrl).then(function(){
    console.log("LOADED!");
    setIsLoading(false);
  });
}, [props.playlistUrl]);
  

Используйте isLoading состояние при рендеринге

 <div
  className={classNames({
    'api-loading': isLoading,
  })}
>
  

Я также предлагаю использовать finally блок цепочки обещаний для завершения загрузки в случае, если обещание отклонено, ваш пользовательский интерфейс не застревает в «состоянии» загрузки.

 React.useEffect(() => {
  setIsLoading(true);
  playlist.loadUrl(props.playlistUrl)
    .then(function() {
      console.log("LOADED!");
    })
    .finally(() => setIsLoading(false));
}, [props.playlistUrl]);
  

Ответ №2:

Вот и все:

 import React from "react";

class PlaylistAPI {
  constructor(data = []) {
    this.data = data;
    this.listeners = [];
  }
  addListener(fn) {
    this.listeners.push(fn);
  }

  removeEventListener(fn) {
    this.listeners = this.listeners.filter(prevFn => prevFn !== fn)
  }

  setPlayList(data) {
    this.data = data;

    this.notif();
  }

  loadUrl(url) {
    console.log("called loadUrl", url, this.data)
  }

  notif() {
    this.listeners.forEach(fn => fn());
  }
  
}

export default function App() {
  const API = React.useMemo(() => new PlaylistAPI(), []);

  React.useEffect(() => {
    API.addListener(loadPlaylist);

    /**
     * Update your playlist and when user job has done, listerners will be called 
     */
    setTimeout(() => {
      API.setPlayList([1,2,3])
    }, 3000)

    return () => {
      API.removeEventListener(loadPlaylist);
    }
  }, [API])

  function loadPlaylist() {
    API.loadUrl("my url");
  }

  return (
    <div className="App">
      <h1>Watching an object by React Hooks</h1>
    </div>
  );
  }
  

Демонстрация в Codesandbox

Комментарии:

1. Хорошо, итак, вы используете здесь запоминание. В чем было бы преимущество использования этого, а не контекста React? Спасибо.

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