React useContext & useReducer 搭配

這篇是學習 React 的 useContext, useReducer 的一種實作方式,將相關程式碼放在一起方便閱讀,只是一個使用的範例提供紀錄與理解,實際開發時建議將各模組拆分出去

基礎實作範例

import React, { useReducer, useContext } from 'react'
import { render } from 'react-dom'

// create Context
const StoreContext = React.createContext()

// action types
const actions = {
  add(state, action) {
    return {
      ...state,
      count: state.count + 1
    }
  }
}

// reducer
// use type to match keys in actions
function storeReducer(state, action) {
  const fn = actions[action.type]
  if (!!fn) return fn.call(state, state, action)
  throw Error(`action type ${action.type} not found`)
}

// StoreProvider
// initialize useReducer and Context Provider
function StoreProvider({ children }) {
  // initialize & bind reducer
  const [state, dispatch] = useReducer(storeReducer, {
    profile: {
      username: 'johnny',
      age: 30
    },
    count: 0,
  })
  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  )
}

// children get context states with useContext
function InfoBlock() {
  const { state, dispatch } = useContext(StoreContext)

  const handlerClick = () => {
    dispatch({ type: 'add' })
    console.log(state)
  }

  return (
    <div onClick={handlerClick}>
      <h3>{state.profile.username}</h3>
      <p>{state.profile.age}</p>
      <p>count {state.count}</p>
    </div>
  )
}

// bind & apply StoreProvider to App
function App() {
  return (
    <StoreProvider>
      <InfoBlock />
    </StoreProvider>
  )
}

render(<App />, document.getElementById('app'))

Constate 使用範例

// context/profile.js
import { useState, useCallback } from 'react';
import constate from 'constate';
import services from '../services';

export const [ProfileProvider, useProfileContext] = constate(({ useModal }) => {
  const [profile, setProfile] = useState({});
  console.log(useModal);

  const getProfile = useCallback(async () => {
    const res = await services.getProfile();
    setProfile(res);
  }, []);

  return [profile, getProfile];
});
// context/modal.js
import { useState, useCallback } from 'react';
import constate from 'constate';

export const [ModalProvider, useModalContext] = constate(() => {
  const [modal, setModal] = useState({
    name: '',
    data: {},
  });

  const updateModal = useCallback((name, data) => {
    setModal((state) => ({
      name,
      data: data || state.data,
    }));
  }, []);

  const closeModal = useCallback(() => {
    setModal(() => ({ name: '', data: {}}));
  }, []);

  return [modal, updateModal, closeModal];
});
// context/index.js
import { ProfileProvider, useProfileContext } from './Profile';
import { ModalProvider, useModalContext } from './Modal';

const Providers = [
  ProfileProvider,
  ModalProvider,
];

const hooks = {
  useProfileContext,
  useModalContext,
};

// wrap all provider, and pass hooks to constate
export default function ConstateProvider(props) {
  const { children } = props;
  let element = children;
  for (let i = 0;i < Providers.length; i++) {
    const Provider = Providers[i];
    element = (
      <Provider {...props} {...hooks}>{element}</Provider>
    );
  }
  return element;
}

export { useProfileContext } from './Profile';
export { useModalContext } from './Modal';
Last Updated:
Contributors: johnnywang