【redux】@reduxjs/tookit快速上手

前言

  • 最近兴起使用redux tookit,赶紧学一下。

@reduxjs/tookit

  • 主要是为了解决redux样板代码太多。
  • 学习此篇请熟练使用redux,否则可能看不懂。

基础使用

  • 首先,每个reducer有自己的作用域namespace,然后由combinedReducer集合成完整的reducer 。每个小reducer叫slice。
  • 使用create-react-app创建项目
  • 首先需要制作store,删除app.tsx中无用代码。

configureStore

  • 创建store由原来createStore变更为configureStore。
  • 中间件啥的以配置形式传入。
  • 其内置了immer与thunk,所以可以以点的形式进行修改,并且可以派发函数。
export interface ConfigureStoreOptions<S = any, A extends Action = AnyAction, M extends Middlewares<S> = Middlewares<S>> {
    
    
    /**
     * A single reducer function that will be used as the root reducer, or an
     * object of slice reducers that will be passed to `combineReducers()`.
     */
    reducer: Reducer<S, A> | ReducersMapObject<S, A>;
    /**
     * An array of Redux middleware to install. If not supplied, defaults to
     * the set of middleware returned by `getDefaultMiddleware()`.
     */
    middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M;
    /**
     * Whether to enable Redux DevTools integration. Defaults to `true`.
     *
     * Additional configuration can be done by passing Redux DevTools options
     */
    devTools?: boolean | DevToolsOptions;
    /**
     * The initial state, same as Redux's createStore.
     * You may optionally specify it to hydrate the state
     * from the server in universal apps, or to restore a previously serialized
     * user session. If you use `combineReducers()` to produce the root reducer
     * function (either directly or indirectly by passing an object as `reducer`),
     * this must be an object with the same shape as the reducer map keys.
     */
    preloadedState?: PreloadedState<CombinedState<NoInfer<S>>>;
    /**
     * The store enhancers to apply. See Redux's `createStore()`.
     * All enhancers will be included before the DevTools Extension enhancer.
     * If you need to customize the order of enhancers, supply a callback
     * function that will receive the original array (ie, `[applyMiddleware]`),
     * and should return a new array (such as `[applyMiddleware, offline]`).
     * If you only need to add middleware, you can use the `middleware` parameter instead.
     */
    enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback;
}
  • 创建store.ts导出store :
export const store = configureStore({
    
    
    reducer: {
    
    
      counter: counterReducer,
    },
});
  • counterReducer是由createSlice创建出来的,而createSlice通过你的传递,可以通过返回对象自动生成其reducer、actions。

createSlice

  • 先制作个基础例子,createSlice可以传入reducers的逻辑再生成reducer与action:
/*
 * @Author: yehuozhili
 * @Date: 2021-08-14 15:18:10
 * @LastEditors: yehuozhili
 * @LastEditTime: 2021-08-14 16:07:17
 * @FilePath: \learnrtk\src\store.ts
 */

import {
    
     configureStore, createSlice } from "@reduxjs/toolkit";

export interface CounterState {
    
    
    value: number;
    status: 'idle' | 'loading' | 'failed';
  }
  const initialState: CounterState = {
    
    
    value: 0,
    status: 'idle',
  };

export const counterSlice = createSlice({
    
    
    name: 'counter',
    initialState,
    reducers: {
    
    
      increment: (state) => {
    
    
        state.value += 1;
      },
      decrement: (state) => {
    
    
        state.value -= 1;
      },
      incrementByAmount: (state, action) => {
    
    
        state.value += action.payload;
      },
    },
 
});

//useSelector获取值
export const selectCount = (state: RootState) => state.counter.value;
//actions
export const {
    
     increment, decrement, incrementByAmount } = counterSlice.actions;

const counterReducer = counterSlice.reducer

export const store = configureStore({
    
    
    reducer: {
    
    
      counter: counterReducer,
    },
});

export type RootState = ReturnType<typeof store.getState>;
  • name则是namespace,生成的丢给configStore的reducer即可。
  • 然后在根路径下链接store,这个和以前是一样的:
  <Provider store={store}>
      <App />
  </Provider>
  • 然后我们使用counter渲染出来,再派发去修改状态:
import {
    
     useDispatch, useSelector } from 'react-redux';
import './App.css';
import {
    
     decrement, increment, selectCount } from './store';


function App() {
    
    
  const dispatch = useDispatch()
  const state = useSelector(selectCount)
  return (
    <div className="App">
      {
    
    
        state
      }
      <button onClick={
    
    ()=>{
    
    
        dispatch(increment())
      }}>+</button>
       <button onClick={
    
    ()=>{
    
    
        dispatch(decrement())
      }}>-</button>
    </div>
  );
}

export default App;
  • 此时加减都正常了。
  • 下面使用异步加减:

createAsyncThunk

  • createAsyncThunk是一个便于创建异步reducer处理逻辑的函数,它自动生成3中actionCreator ,pending,fullfilled,rejected :
export declare type AsyncThunk<Returned, ThunkArg, ThunkApiConfig extends AsyncThunkConfig> = AsyncThunkActionCreator<Returned, ThunkArg, ThunkApiConfig> & {
    
    
    pending: AsyncThunkPendingActionCreator<ThunkArg, ThunkApiConfig>;
    rejected: AsyncThunkRejectedActionCreator<ThunkArg, ThunkApiConfig>;
    fulfilled: AsyncThunkFulfilledActionCreator<Returned, ThunkArg, ThunkApiConfig>;
    typePrefix: string;
};
  • 而它返回值,本身就是个action:
  function fetchCount(amount = 1) {
    
    
    return new Promise<{
    
     data: number }>((resolve) =>
      setTimeout(() => resolve({
    
     data: amount }), 500)
    );
  }
  
  export const incrementAsync = createAsyncThunk(
    'counter/fetchCount',
    async (amount?: number) => {
    
    
      const response = await fetchCount(amount);
      return response.data;
    }
  );
  • 可以直接进行派发:
import {
    
     decrement, increment, incrementAsync, selectCount, selectCountStatus } from './store';


function App() {
    
    
  const dispatch = useDispatch()
  const state = useSelector(selectCount)
  const st = useSelector(selectCountStatus)
  return (
    <div className="App">
      {
    
    
       st === 'loading'?'loading':  state
      }
      <button onClick={
    
    ()=>{
    
    
        dispatch(increment())
      }}>+</button>
       <button onClick={
    
    ()=>{
    
    
        dispatch(decrement())
      }}>-</button>
       <button onClick={
    
    ()=>{
    
    
        dispatch(incrementAsync())
      }}>+</button>
    </div>
  );
}

export default App;
  • 在createSlice时,可使用extraReducer进行处理:
export const counterSlice = createSlice({
    
    
    name: 'counter',
    initialState,
    reducers: {
    
    
      increment: (state) => {
    
    
        state.value += 1;
      },
      decrement: (state) => {
    
    
        state.value -= 1;
      },
      incrementByAmount: (state, action) => {
    
    
        state.value += action.payload;
      },
    },
    extraReducers:(builder)=>{
    
    
        builder.addCase(incrementAsync.pending,(state) => {
    
    
            state.status = 'loading';
          })
          builder.addCase(incrementAsync.fulfilled,(state,action) => {
    
    
            state.status = 'idle';
            state.value += action.payload;
          })
    }
});

  • builder除了addCase,还有addMatcher以及addDefaultCase,类似于链式调用的使用方式,可直接再后面继续点。
  • 这样就可以在异步的不同阶段修改状态。
  • 当然也可以手动编写异步函数

createReducer&createAction

  • 前面看了createSlice的强大能力,当然你也可以手动写action,使用createAction函数可以帮你自动生成对象,让你可以使用.type来取得所要的action,而不需要像以前引入常量了。
const actionCreator = createAction('SOME_ACTION_TYPE')

console.log(actionCreator.toString())
// "SOME_ACTION_TYPE"

console.log(actionCreator.type)
// "SOME_ACTION_TYPE"
  • 而reducer则可以使用builder进行点链接:
const reducer = createReducer({
    
    }, (builder) => {
    
    
  // actionCreator.toString() will automatically be called here
  // also, if you use TypeScript, the action type will be correctly inferred
  builder.addCase(actionCreator, (state, action) => {
    
    })

  // Or, you can reference the .type field:
  // if using TypeScript, the action type cannot be inferred that way
  builder.addCase(actionCreator.type, (state, action) => {
    
    })
})

creatApi

  • creatApi可以返回自定义hook,reducer,reducer前缀,中间件:
// Need to use the React-specific entry point to import createApi
import {
    
     createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import {
    
     Pokemon } from './types'

// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
    
    
  reducerPath: 'pokemonApi',
  baseQuery: fetchBaseQuery({
    
     baseUrl: 'https://pokeapi.co/api/v2/' }),
  endpoints: (builder) => ({
    
    
    getPokemonByName: builder.query<Pokemon, string>({
    
    
      query: (name) => `pokemon/${
      
      name}`,
    }),
  }),
})

// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const {
    
     useGetPokemonByNameQuery } = pokemonApi
import {
    
     configureStore } from '@reduxjs/toolkit'
// Or from '@reduxjs/toolkit/query/react'
import {
    
     setupListeners } from '@reduxjs/toolkit/query'
import {
    
     pokemonApi } from './services/pokemon'

export const store = configureStore({
    
    
  reducer: {
    
    
    // Add the generated reducer as a specific top-level slice
    [pokemonApi.reducerPath]: pokemonApi.reducer,
  },
  // Adding the api middleware enables caching, invalidation, polling,
  // and other useful features of `rtk-query`.
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(pokemonApi.middleware),
})

// optional, but required for refetchOnFocus/refetchOnReconnect behaviors
// see `setupListeners` docs - takes an optional callback as the 2nd arg for customization
setupListeners(store.dispatch)
  • 在组件中使用类似于umi的useRequest:
import * as React from 'react'
import {
    
     useGetPokemonByNameQuery } from './services/pokemon'

export default function App() {
    
    
  // Using a query hook automatically fetches data and returns query values
  const {
    
     data, error, isLoading } = useGetPokemonByNameQuery('bulbasaur')
  // Individual hooks are also accessible under the generated endpoints:
  // const { data, error, isLoading } = pokemonApi.endpoints.getPokemonByName.useQuery('bulbasaur')

  return (
    <div className="App">
      {
    
    error ? (
        <>Oh no, there was an error</>
      ) : isLoading ? (
        <>Loading...</>
      ) : data ? (
        <>
          <h3>{
    
    data.species.name}</h3>
          <img src={
    
    data.sprites.front_shiny} alt={
    
    data.species.name} />
        </>
      ) : null}
    </div>
  )
}

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/119674313
今日推荐