Evolusi Pengurusan Keadaan React: Dari Tempatan kepada Async

WBOY
Lepaskan: 2024-08-21 06:49:02
asal
1078 orang telah melayarinya

Jadual Kandungan

  • pengenalan
  • Negeri Tempatan
    • Komponen Kelas
    • Komponen Berfungsi
    • gunakanReducer Hook
  • Negara Global
    • Apakah Negara Global?
    • Bagaimana Cara Menggunakannya?
    • Jalan Utama
    • Cara Mudah
    • Cara Yang Salah
  • Negeri Async
  • Kesimpulan

pengenalan

Hai!

Artikel ini membentangkan gambaran keseluruhan tentang cara State diuruskan dalam React Applications beribu-ribu tahun yang lalu apabila Komponen Kelas menguasai dunia dan komponen berfungsi hanyalah idea yang berani, sehingga kebelakangan ini , apabila paradigma baharu State telah muncul: Async State.

Negeri Tempatan

Baiklah, semua orang yang telah bekerja dengan React tahu apa itu Negeri Tempatan.

Saya tidak tahu apa itu
Negeri Tempatan ialah keadaan bagi satu Komponen.

Setiap kali keadaan dikemas kini, komponen itu dipaparkan semula.


Anda mungkin pernah bekerja dengan struktur purba ini:

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>;
  }
}
}
Salin selepas log masuk

Mungkin moden berfungsi satu:

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>;
};
Salin selepas log masuk

Atau malah "lebih diterima" satu? (Semestinya lebih jarang berlaku)

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' });
};
Salin selepas log masuk

Yang boleh membuatkan anda tertanya-tanya...

Mengapa godam saya akan menulis pengurang kompleks ini untuk satu komponen?

Nah, React mewarisi cangkuk hodoh ini yang dipanggil useReducer daripada alat yang sangat penting yang dipanggil Redux.

Jika anda pernah berhadapan dengan Pengurusan Negara Global dalam React, anda mesti pernah mendengar tentang Redux.

Ini membawa kita ke topik seterusnya: Pengurusan Negeri Global.

Negara Global

Pengurusan Negara Global ialah salah satu mata pelajaran kompleks pertama apabila mempelajari React.

Apa itu?

Ia boleh menjadi berbilang perkara, dibina dalam pelbagai cara, dengan perpustakaan yang berbeza.

Saya suka mentakrifkannya sebagai:

Satu objek JSON, diakses dan diselenggara oleh mana-mana Komponen aplikasi.

const globalState = { 
  isUnique: true,
  isAccessible: true,
  isModifiable: true,
  isFEOnly: true
}
Salin selepas log masuk

Saya suka menganggapnya sebagai:

Pangkalan Data Hadapan Tanpa SQL.

Betul, Pangkalan Data. Di sinilah anda menyimpan data aplikasi, yang komponen anda boleh membaca/menulis/kemas kini/padam.

Saya tahu, secara lalai, keadaan akan dicipta semula apabila pengguna memuat semula halaman, tetapi itu mungkin bukan perkara yang anda mahukannya lakukan, dan jika anda meneruskan data di suatu tempat (seperti LocalStorage), anda mungkin mahu untuk mengetahui tentang penghijrahan untuk mengelak daripada memecahkan apl setiap penggunaan baharu.

Saya suka menggunakannya sebagai:

Portal berbilang dimensi, di mana komponen boleh menghantar perasaan mereka dan memilih atribut mereka. Semuanya, di mana-mana, sekali gus.

Bagaimana cara menggunakannya?

Cara utama

Redux

Ia adalah standard industri.

Saya telah bekerja dengan React, TypeScript dan Redux selama 7 tahun. Setiap projek yang saya telah bekerjasama secara profesional menggunakan Redux.

Sebilangan besar orang yang saya temui yang bekerja dengan React, gunakan Redux.

Alat yang paling banyak disebut dalam kedudukan terbuka React di Trampar de Casa ialah Redux.

Alat React State Management yang paling popular ialah...

The Evolution of React State Management: From Local to Async

Redux

The Evolution of React State Management: From Local to Async

Jika anda ingin bekerja dengan React, anda harus belajar Redux.
Jika anda kini bekerja dengan React, anda mungkin sudah tahu.

Ok, berikut ialah cara kami biasanya mengambil data menggunakan Redux.

Penafian
"Apa? Adakah ini masuk akal? Redux adalah untuk menyimpan data, bukan untuk mengambil, bagaimana F anda akan mengambil data dengan Redux?"

Jika anda memikirkan perkara ini, saya mesti memberitahu anda:

Saya sebenarnya tidak mengambil data dengan Redux.
Redux akan menjadi kabinet untuk aplikasi, ia akan menyimpan ~kasut~ menyatakan yang berkaitan secara langsung dengan mengambil, itulah sebabnya saya menggunakan frasa yang salah ini: "ambil data menggunakan 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;
  }
};
Salin selepas log masuk

Kini di bahagian UI, kami menyepadukan dengan tindakan menggunakan useDispatch dan useSelector:

// 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>
  );
};
Salin selepas log masuk

Jika Commits.tsx ialah satu-satunya komponen yang diperlukan untuk mengakses senarai komit, anda tidak seharusnya menyimpan data ini pada Keadaan Global. Ia boleh menggunakan negeri tempatan sebaliknya.

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>;
}
Salin selepas log masuk

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')
);
Salin selepas log masuk

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 });
    }
  },
}));
Salin selepas log masuk

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>
  );
}

Salin selepas log masuk

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> 
    ); 
};
Salin selepas log masuk

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

Untuk artikel ini, saya akan menggunakan React-Query.

Saya mengesyorkan anda untuk mengakses dokumen setiap alat ini untuk memahami dengan lebih baik masalah yang mereka selesaikan.

Ini kodnya:

Tiada lagi tindakan, tiada lagi penghantaran, tiada lagi Negeri Global

untuk mengambil data.

Inilah yang anda perlu lakukan dalam fail App.tsx anda untuk mempunyai React-Query dikonfigurasikan dengan betul:

Anda lihat, Async State

adalah istimewa.

Ia seperti kucing Schrödinger – anda tidak tahu keadaannya sehingga anda memerhatikannya (atau menjalankannya).

Tetapi tunggu, jika kedua-dua komponen memanggil useCommits dan useCommits memanggil titik akhir API, adakah ini bermakna terdapat DUA permintaan yang sama untuk memuatkan data yang sama?

Jawapan Ringkas: tidak!

Jawapan Panjang: React Query adalah hebat. Ia secara automatik mengendalikan situasi ini untuk anda, ia disertakan dengan caching prakonfigurasi yang cukup pintar untuk mengetahui bila mendapatkan semula

data anda atau hanya menggunakan cache.

Ia juga sangat boleh dikonfigurasikan supaya anda boleh mengubah suai agar muat 100% daripada keperluan aplikasi anda.

Kini kami mempunyai komponen kami sentiasa bersedia untuk isLoading atau isError dan kami memastikan Keadaan Global kurang tercemar dan mempunyai beberapa ciri yang cukup kemas di luar kotak.

Kesimpulan

Sekarang anda tahu perbezaan antara Tempatan, Global dan Async State

.


Tempatan -> Komponen Sahaja.
Global -> Single-Json-NoSQL-DB-For-The-FE.

Async -> Data luaran, Schrodinger seperti kucing, tinggal di luar aplikasi FE yang memerlukan Pemuatan dan boleh mengembalikan Ralat.

Saya harap anda menikmati artikel ini, beritahu saya jika anda mempunyai pendapat yang berbeza atau sebarang maklum balas yang membina, sorakan!

Atas ialah kandungan terperinci Evolusi Pengurusan Keadaan React: Dari Tempatan kepada Async. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan
Tentang kita Penafian Sitemap
Laman web PHP Cina:Latihan PHP dalam talian kebajikan awam,Bantu pelajar PHP berkembang dengan cepat!