React 状態管理の進化: ローカルから非同期へ

WBOY
リリース: 2024-08-21 06:49:02
オリジナル
1021 人が閲覧しました

目次

  • 導入
  • 地元の州
    • クラスコンポーネント
    • 機能部品
    • 使用リデューサーフック
  • 世界の状態
    • グローバルステートとは何ですか?
    • 使用方法
    • メインウェイ
    • 簡単な方法
    • 間違った方法
  • 非同期状態
  • 結論

導入

こんにちは!

この記事では、クラス コンポーネントが世界を支配し、機能コンポーネント が最近まで大胆なアイデアにすぎなかった数千年前に、React アプリケーションで 状態 がどのように管理されていたかについて概要を説明します。 状態の新しいパラダイムが出現したとき: 非同期状態.

地元の州

はい、すでに React を使ったことがある人なら誰でも、ローカル状態が何であるかを知っています。

それが何なのか分かりません
ローカル状態は、単一のコンポーネントの状態です。

状態が更新されるたびに、コンポーネントは再レンダリングされます。


あなたはこの古代の構造物を扱ったことがあるかもしれません:

class CommitList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isLoading: false,
      commits: [],
      error: null
    };
  }

  componentDidMount() {
    this.fetchCommits();
  }

  fetchCommits = async () => {
    this.setState({ isLoading: true });
    try {
      const response = await fetch('https://api.github.com/repos/facebook/react/commits');
      const data = await response.json();
      this.setState({ commits: data, isLoading: false });
    } catch (error) {
      this.setState({ error: error.message, isLoading: false });
    }
  };

  render() {
    const { isLoading, commits, error } = this.state;

    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error}</div>;

    return (
      <div>
        <h2>Commit List</h2>
        <ul>
          {commits.map(commit => (
            <li key={commit.sha}>{commit.commit.message}</li>
          ))}
        </ul>
        <TotalCommitsCount count={commits.length} />
      </div>
    );
  }
}

class TotalCommitsCount extends Component {
  render() {
    return <div>Total commits: {this.props.count}</div>;
  }
}
}
ログイン後にコピー

おそらくモダン 機能的 のもの:

const CommitList = () => {
  const [isLoading, setIsLoading] = useState(false);
  const [commits, setCommits] = useState([]);
  const [error, setError] = useState(null);

  // To update state you can use setIsLoading, setCommits or setUsername.
  // As each function will overwrite only the state bound to it.
  // NOTE: It will still cause a full-component re-render
  useEffect(() => {
    const fetchCommits = async () => {
      setIsLoading(true);
      try {
        const response = await fetch('https://api.github.com/repos/facebook/react/commits');
        const data = await response.json();
        setCommits(data);
        setIsLoading(false);
      } catch (error) {
        setError(error.message);
        setIsLoading(false);
      }
    };

    fetchCommits();
  }, []);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <h2>Commit List</h2>
      <ul>
        {commits.map(commit => (
          <li key={commit.sha}>{commit.commit.message}</li>
        ))}
      </ul>
      <TotalCommitsCount count={commits.length} />
    </div>
  );
};

const TotalCommitsCount = ({ count }) => {
  return <div>Total commits: {count}</div>;
};
ログイン後にコピー

それとも「もっと受け入れられる」ものでしょうか? (間違いなくもっと珍しいですが)

const initialState = {
  isLoading: false,
  commits: [],
  userName: ''
};

const reducer = (state, action) => {
  switch (action.type) {
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload };
    case 'SET_COMMITS':
      return { ...state, commits: action.payload };
    case 'SET_USERNAME':
      return { ...state, userName: action.payload };
    default:
      return state;
  }
};

const CommitList = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { isLoading, commits, userName } = state;

  // To update state, use dispatch. For example:
  // dispatch({ type: 'SET_LOADING', payload: true });
  // dispatch({ type: 'SET_COMMITS', payload: [...] });
  // dispatch({ type: 'SET_USERNAME', payload: 'newUsername' });
};
ログイン後にコピー

それは疑問に思うかもしれません...

単一コンポーネントに対してこの複雑なリデューサーを作成する必要があるのはなぜですか?ハック

そうですね、React は、

Redux という非常に重要なツールから useReducer と呼ばれるこの 醜い フックを継承しました。

React で

グローバル ステート管理 を扱う必要があった場合は、Redux について聞いたことがあるはずです。

これで次のトピック、グローバル状態管理に移ります。

世界の状態

グローバル状態管理は、React を学習する際に最初に行う複雑な主題の 1 つです。

それは何ですか?

さまざまなライブラリを使用して、さまざまな方法で構築された複数のものが存在する可能性があります。

私はそれを次のように定義したいと思います:

アプリケーションのコンポーネントによってアクセスおよび維持される単一の JSON オブジェクト。


const globalState = { 
  isUnique: true,
  isAccessible: true,
  isModifiable: true,
  isFEOnly: true
}
ログイン後にコピー

私は次のように考えるのが好きです:

フロントエンドの

SQL を使用しない データベース。

そうです、データベースです。ここには、コンポーネントが読み取り/書き込み/更新/削除できるアプリケーション データが保存されます。

デフォルトでは、ユーザーがページをリロードするたびに状態が再作成されることはわかっていますが、それは望んでいることではない可能性があり、データをどこか (localStorage など) に永続化している場合は、次のようにする必要があるかもしれません。新しくデプロイするたびにアプリが壊れることを避けるための移行について学びます。

私は次のように使用するのが好きです:

コンポーネントが感情を

発信し、属性を選択できる多次元ポータル。すべてを、どこでも、一度に。

使い方は?

主な方法

リダックス

これは業界標準です。

私は React、TypeScript、および Redux を 7 年間使用してきました。私が専門的にと一緒に取り組んだすべてのプロジェクトはReduxを使用しています。

私が会った React を使っている人の大多数は Redux を使用しています。

Trampar de Casa の React 募集ポジションで最も言及されているツールは Redux です。

最も人気のある React 状態管理ツールは...

The Evolution of React State Management: From Local to Async

Redux

The Evolution of React State Management: From Local to Async

React を使って作業したい場合は、Redux を学ぶ必要があります。
現在 React を使用している場合は、おそらくすでにご存知でしょう。

わかりました。Redux を使用して通常どのようにデータを取得するかを示します。

免責事項
「何ですか? これには意味がありますか? Redux はデータを保存するものであり、フェッチするものではありません。Redux を使用してデータをフェッチするにはどうすればよいでしょうか?」

これについて考えたことがあるなら、こう言わなければなりません:

実際には Redux でデータをフェッチしていません。
Redux はアプリケーションのキャビネットになります。フェッチに直接関連する ~shoes~ 状態を保存します。それが、私がこの間違ったフレーズを使用した理由です。「Redux を使用してデータをフェッチする」


// actions
export const SET_LOADING = 'SET_LOADING';
export const setLoading = (isLoading) => ({
  type: SET_LOADING,
  payload: isLoading,
});

export const SET_ERROR = 'SET_ERROR';
export const setError = (isError) => ({
  type: SET_ERROR,
  payload: isError,
});

export const SET_COMMITS = 'SET_COMMITS';
export const setCommits = (commits) => ({
  type: SET_COMMITS,
  payload: commits,
});


// To be able to use ASYNC action, it's required to use redux-thunk as a middleware
export const fetchCommits = () => async (dispatch) => {
  dispatch(setLoading(true));
  try {
    const response = await fetch('https://api.github.com/repos/facebook/react/commits');
    const data = await response.json();
    dispatch(setCommits(data));
    dispatch(setError(false));
  } catch (error) {
    dispatch(setError(true));
  } finally {
    dispatch(setLoading(false));
  }
};

// the state shared between 2-to-many components
const initialState = {
  isLoading: false,
  isError: false,
  commits: [],
};

// reducer
export const rootReducer = (state = initialState, action) => {
  // This could also be actions[action.type].
  switch (action.type) {
    case SET_LOADING:
      return { ...state, isLoading: action.payload };
    case SET_ERROR:
      return { ...state, isError: action.payload };
    case SET_COMMITS:
      return { ...state, commits: action.payload };
    default:
      return state;
  }
};
ログイン後にコピー

UI 側では、useDispatchuseSelector:
を使用してアクションと統合します。

// Commits.tsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCommits } from './action';

export const Commits = () => {
  const dispatch = useDispatch();
  const { isLoading, isError, commits } = useSelector(state => state);

  useEffect(() => {
    dispatch(fetchCommits());
  }, [dispatch]);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error while trying to fetch commits.</div>;

  return (
    <ul>
      {commits.map(commit => (
        <li key={commit.sha}>{commit.commit.message}</li>
      ))}
    </ul>
  );
};
ログイン後にコピー

Commits.tsx がコミット リストにアクセスする必要がある唯一のコンポーネントである場合、このデータをグローバル状態に保存すべきではありません。代わりにローカル状態を使用することもできます。

But let's suppose you have other components that need to interact with this list, one of them may be as simple as this one:

// TotalCommitsCount.tsx
import React from 'react';
import { useSelector } from 'react-redux';

export const TotalCommitsCount = () => {
  const commitCount = useSelector(state => state.commits.length);
  return <div>Total commits: {commitCount}</div>;
}
ログイン後にコピー

Disclaimer
In theory, this piece of code would make more sense living inside Commits.tsx, but let's assume we want to display this component in multiple places of the app and it makes sense to put the commits list on the Global State and to have this TotalCommitsCount component.

With the index.js component being something like this:

import React from 'react';
import ReactDOM from 'react-dom';
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import { Commits } from "./Commits"
import { TotalCommitsCount } from "./TotalCommitsCount"

export const App = () => (
    <main>
        <TotalCommitsCount />
        <Commits />
    </main>
)

const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
ログイン後にコピー

This works, but man, that looks overly complicated for something as simple as fetching data right?

Redux feels a little too bloated to me.

You're forced to create actions and reducers, often also need to create a string name for the action to be used inside the reducer, and depending on the folder structure of the project, each layer could be in a different file.

Which is not productive.

But wait, there is a simpler way.

The simple way

Zustand

At the time I'm writing this article, Zustand has 3,495,826 million weekly downloads, more than 45,000 stars on GitHub, and 2, that's right, TWO open Pull Requests.

ONE OF THEM IS ABOUT UPDATING IT'S DOC
The Evolution of React State Management: From Local to Async

If this is not a piece of Software Programming art, I don't know what it is.

Here's how to replicate the previous code using Zustand.

// store.js
import create from 'zustand';

const useStore = create((set) => ({
  isLoading: false,
  isError: false,
  commits: [],
  fetchCommits: async () => {
    set({ isLoading: true });
    try {
      const response = await fetch('https://api.github.com/repos/facebook/react/commits');
      const data = await response.json();
      set({ commits: data, isError: false });
    } catch (error) {
      set({ isError: true });
    } finally {
      set({ isLoading: false });
    }
  },
}));
ログイン後にコピー

This was our Store, now the UI.

// Commits.tsx
import React, { useEffect } from 'react';
import useStore from './store';

export const Commits = () => {
  const { isLoading, isError, commits, fetchCommits } = useStore();

  useEffect(() => {
    fetchCommits();
  }, [fetchCommits]);

  if (isLoading) return <div>Loading...</div>;
  if (isError) return <div>Error occurred</div>;

  return (
    <ul>
      {commits.map(commit => (
        <li key={commit.sha}>{commit.commit.message}</li>
      ))}
    </ul>
  );
}

ログイン後にコピー

And last but not least.

// TotalCommitsCount.tsx
import React from 'react'; 
import useStore from './store'; 
const TotalCommitsCount = () => { 
    const totalCommits = useStore(state => state.commits.length);
    return ( 
        <div> 
            <h2>Total Commits:</h2> <p>{totalCommits}</p> 
        </div> 
    ); 
};
ログイン後にコピー

There are no actions and reducers, there is a Store.

And it's advisable to have slices of Store, so everything is near to the feature related to the data.

It works perfect with a folder-by-feature folder structure.
The Evolution of React State Management: From Local to Async

The wrong way

I need to confess something, both of my previous examples are wrong.

And let me do a quick disclaimer: They're not wrong, they're outdated, and therefore, wrong.

This wasn't always wrong though. That's how we used to develop data fetching in React applications a while ago, and you may still find code similar to this one out there in the world.

But there is another way.

An easier one, and more aligned with an essential feature for web development: Caching. But I'll get back to this subject later.

Currently, to fetch data in a single component, the following flow is required:
The Evolution of React State Management: From Local to Async

What happens if I need to fetch data from 20 endpoints inside 20 components?

  • 20x isLoading + 20x isError + 20x actions to mutate this properties.

What will they look like?

With 20 endpoints, this will become a very repetitive process and will cause a good amount of duplicated code.

What if you need to implement a caching feature to prevent recalling the same endpoint in a short period? (or any other condition)

Well, that will translate into a lot of work for basic features (like caching) and well-written components that are prepared for loading/error states.

This is why Async State was born.

Async State

Before talking about Async State I want to mention something. We know how to use Local and Global state but at this time I didn't mention what should be stored and why.

The Global State example has a flaw and an important one.

The TotalCommitsCount component will always display the Commits Count, even if it's loading or has an error.

If the request failed, there's no way to know that the Total Commits Count is 0, so presenting this value is presenting a lie.

In fact, until the request finishes, there is no way to know for sure what's the Total Commits Count value.

This is because the Total Commits Count is not a value we have inside the application. It's external information, async stuff, you know.

We shouldn't be telling lies if we don't know the truth.

That's why we must identify Async State in our application and create components prepared for it.

We can do this with React-Query, SWR, Redux Toolkit Query and many others.

The Evolution of React State Management: From Local to Async

この記事では、React-Query を使用します。

どの問題が解決されるのかをよりよく理解するために、これらの各ツールのドキュメントにアクセスすることをお勧めします。

コードは次のとおりです:

データを取得するためのアクション、ディスパッチ、グローバル状態

はもう必要ありません。

React-Query を適切に構成するには、App.tsx ファイルで次のことを行う必要があります。

ご存知のように、非同期状態

は特別です。

それはシュレディンガーの猫のようなものです。観察する (または実行する) まで状態はわかりません。

しかし、両方のコンポーネントが useCommits を呼び出しており、useCommits が API エンドポイントを呼び出している場合、これは、同じデータをロードするために 2 つの同一のリクエストが存在することを意味するのでしょうか?

短い答え: いいえ!

長い答え: React Query は素晴らしいです。この状況は自動的に処理され、データをいつ再フェッチ

するか、単にキャッシュを使用するかを判断できる、事前設定されたキャッシュが付属しています。

また、非常に構成可能であるため、アプリケーションのニーズに 100% 適合するように調整できます。

これで、コンポーネントは常に isLoading または isError に対応できるようになり、グローバル状態の汚染が少なくなり、すぐに使用できる非常に優れた機能がいくつか追加されました。

結論

これで、ローカルグローバル、および 非同期状態

の違いが分かりました。


ローカル ->コンポーネントのみ。
グローバル -> Single-Json-NoSQL-DB-For-The-FE.

非同期 ->外部データ (シュレディンガーの猫のようなもの)。FE アプリケーションの外側に存在し、読み込みを必要とし、エラーを返す可能性があります。

この記事を楽しんでいただければ幸いです。異なる意見や建設的なフィードバックがありましたら、ぜひお知らせください。

以上がReact 状態管理の進化: ローカルから非同期への詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!