react hooks如何初步替代redux

看到標題,你會想到什麼???有什麼解決方案
react

  • useReduce + useContext
  • react-redux 提供的 useSelector , useDispatch

咱們來經過class版本往react hook版本一步步遞進
咱們來經過使用複選框點擊事件來看結果redux

react hook 替換 redux 解決方案

class版本

import { ToggleState } from '../store/reducers/toggle';
import { RootState } from '../store/reducers';

type Props = ToggleState & typeof action;
class Toggle extends React.Component<Props, any> {
  render(){
    const { ui, toggleSwitch } = this.props;
    return (
      <div>
        <div>{JSON.stringify(ui)}</div>
        <input
          type="checkbox"
          value={ui.toggle ? 'true': 'false'}
          onChange={(e)=>toggleSwitch(e)}
        />
      </div>
    );
  }
}

const mapStateToProps = (state: RootState):ToggleState => state.toggle;

export default connect(mapStateToProps, action)(Toggle);
複製代碼

咱們用 class版本先把功能作出來
觸發完一次支持sum值加1, toggle根據當前checked的狀態設置

函數組件版本:中間版本1

import action from '../store/actions/toggle'
import { ToggleState } from '../store/reducers/toggle';
import { RootState } from '../store/reducers';

type Props = typeof action;
const Toggle2 = ({toggleSwitch}:Props) => {
  const { ui }: ToggleState = useSelector((state: RootState):ToggleState => state.toggle);
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e)=>toggleSwitch(e)}
      />
    </div>
  );
}

export default connect(null, action)(Toggle2);
複製代碼

咱們將class組件替換成了函數組件,其餘的不變
還不是咱們想要的結果,繼續優化咯~~~數組

react-redux hook useSelector useDispatch

  • 使用 useSelector hook,咱們能夠讀取咱們的狀態
  • useDispatch hook 讓咱們執行 redux 操做
type Props = typeof action;
// 使用 useSelector hook,咱們能夠讀取咱們的狀態。
// useDispatch hook 讓咱們執行 redux 操做
// 咱們能夠在任何事件監聽器中調用 dispatch 函數。
const Toggle3 = () => {
  const { ui }: ToggleState = useSelector((state: RootState):ToggleState => state.toggle);
  const dispatch = useDispatch();
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default Toggle3;
複製代碼

固然這邊我有其餘class組件例子使用了redux,咱們但願整改是一點點改掉,因此確定會出現一部分使用react hook函數組件, 一部分使用class組件promise

let reducers: ReducersMapObject<ReducerState, AnyAction> = {
  counter1,
  counter2,
  toggle
}

export type RootState = {
  [P in keyof typeof reducers]: ReturnType<typeof reducers[P]>
}
複製代碼

使用 useSelector 獲取到對應的store狀態使用, useDispatch 幫助咱們獲取到store的dispatch方法,幫助咱們調用對應的reducerbash

useContext + useReducer版本

// ====== ToggleReducer組件 ======
import { ContextParam, AppContext } from './test';
const ToggleReducer = () => {
  const { state, dispatch } = useContext<ContextParam>(AppContext);
  const { ui } = state;
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch!({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default ToggleReducer;

// ====== ToggleReducer1組件 ======
import { ContextParam, AppContext } from './test';
const ToggleReducer1 = () => {
  const { state, dispatch } = useContext<ContextParam>(AppContext);
  const { ui } = state;
  return (
    <div>
      <div>{JSON.stringify(ui)}</div>
      <input
        type="checkbox"
        value={ui.toggle ? 'true': 'false'}
        onChange={(e: ChangeEvent<HTMLInputElement>)=>dispatch!({type: 'toggleSwitch', value: e.currentTarget.checked})}
      />
    </div>
  );
}
export default ToggleReducer1;

// ====== Test組件 ======
export interface ContextParam { 
  state?: any,
  dispatch?: Dispatch<AnyAction>
}
export const AppContext: Context<ContextParam> = React.createContext({});
const Test = () => {
  const [state, dispatch] = useReducer(reducers, {
    ui:{
      toggle: false,
      sum: 0
    }
  });
  return (
    <AppContext.Provider value={{state, dispatch}}>
      <ToggleReducer />
      <ToggleReducer1 />
    </AppContext.Provider>
  );
}
複製代碼

使用上useContext,useReducer 混合以後,效果可見:異步

有人要說了,不能直接經過 useReducer就實現redux功能麼?
你能夠試試,試完以後能夠發現,不使用Context 上下文和useContext去幫助你,能夠了解下react-redux提供的功能就知道了,還真作不到

redux中間件hooks化

說到react hook替換到老式的redux了,若是到後期了,全部的都得改掉,那麼中間件沒提供方案怎麼辦,咱們能夠手寫個,也很簡單的呀~ide

useLogger

咱們使用 useLogger 模擬redux-logger中間件功能函數

type Reducer<S, A> = (prevState: S, action: A) => S;
/**
 * useLogger 模擬redux-logger中間件
 * @param reducer 
 * @param initState 
 */
function useLogger(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useReducer(reducer, initState);
  function loggerDispatch(action:any) {
    console.log('修改以前狀態:', state);
    dispath(action);
  }
  useEffect(() => { 
    console.log('修改以後狀態:', state);
  }, [state])
  return [state, loggerDispatch];
}
複製代碼

調用方式優化

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = useLogger(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
    </div>
  )
}
複製代碼

中間件的操做基本上是封裝原有的store.dispatch ui

useThunk

咱們也想異步加載中間件,咱們這邊我就用useThunk來模擬redux-thunk中間件

/**
 * useThunk 模擬redux-thunk中間件
 * thunk的action調用接收2個參數 , 第一個是dispatch,第二個是getState
 * @param reducer 
 * @param initState 
 */
function useThunk(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useLogger(reducer, initState);
  function thunkDispatch(action: any) {
    if (action && typeof action === 'function') {
      return action(thunkDispatch, () => state);
    }
    return dispath(action);
  }
  return [state, thunkDispatch];
}
複製代碼

調用方式

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = useThunk(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispath(() => { 
        setTimeout(() => {
          dispath({ type: 'INCREMENT' })
        }, 1000);
      })}>thunkplus</button>
    </div>
  )
}
複製代碼

usePromise

咱們再來用usePromise模擬下redux-promise

function usePromise(reducer: Reducer<StateParam, any>, initState: StateParam):[StateParam, any] {
  const [state, dispath] = useLogger(reducer, initState);
  function promiseDispatch(action: any) {
    if (action && typeof action.then === 'function') {
      action.then(promiseDispatch)
    }
    dispath(action);
  }
  return [state, promiseDispatch];
}
複製代碼

調用方式

function App() {
  const initState: StateParam = { number: 0 };
  const [state, dispath] = usePromise(reducer, initState)
  return (
    <div>
      <div>number: {state.number}</div>
      <button onClick={() => dispath({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispath({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispath(new Promise(resolve => { 
          setTimeout(() => {
            resolve({ type: 'INCREMENT' })
          }, 1000);
        })
      )}>promiseplus</button>
    </div>
  )
}
複製代碼

咱們挑了幾個常見簡單的中間件入手實踐了一波,簡單demo已經調研,可使用到項目中去了,你們有什麼看法能夠發評論區你們一塊兒討論哈!感謝你的閱讀,比心~~~~

相關文章
相關標籤/搜索