React에는 상태 관리 도구가 필요하지 않습니다.

프로젝트에 REDUX 또는 유사한 도구가 사용되고 있다는 메시지가 종종 표시됩니다.나는 보통 지금처럼 갈고리와 상하문 API를 사용하지 않을 것이다. 왜냐하면 너는 그것을 필요로 하지 않기 때문이다.
그러나 context API는 보통 성능 문제를 가져오고 정확하게 사용하기도 어색하기 때문에 오늘은 흔한 문제를 피하고 타협하지 않고 자신의 상태 관리 도구를 구축하는 방법을 보여 드리겠습니다.

순진한 해결 방안
기본 사상은 하나의 구성 요소의 상태를 관리하고 상하문을 통해 전체 구성 요소를 전달하는 것이다. 이렇게 하면 모든 하위 구성 요소가 그것에 접근할 수 있기 때문에 우리는 도구 탐색을 피할 수 있다.
export const StateContext = createContext(null);
const Provider = () => {
  return (
    <StateContext.Provider value={state}>
      <ChildComponent />
    </StateContext.Provider>
  )
}

할당 사용
그러나 하위 대상의 상태를 수정하는 방법도 필요합니다. 단일 함수를 상하문에 전달할 수 있지만, 저는 개인적으로 이런 것을 좋아하지 않습니다. 왜냐하면 상태가 곧 복잡해지기 때문입니다.나는 이벤트 분배를 좋아한다. (REDUX와 유사하기 때문에, 우리는 기본적으로 함수를 전달한다. 당신은 그것을 사용하여 필요한 모든 다른 조작을 분배할 수 있다.우리는 상태와 같은 상하문을 통해 그것을 전달할 수 있지만, 나는 그것을 상태와 혼합하는 것을 좋아하지 않기 때문에, 나는 하나의 단독 상하문을 통해 그것을 전달한다.
const StateContext = createContext(null);
const DispatchContext = createContext(null);

export const Provider = () => {
  const [state, setState] = useState(...)

  const dispatch = (action) => {
    switch (action.type) {
      case 'CHANGE_STATE':
        setState(action.payload)
        break;
      ...
    }
  }

  return (
    <StateContext.Provider value={{state, ...}}>
      <DispatchContext.Provider value={dispatch}>
        <ChildComponent />
      </DispatchContext.Provider>
    </StateContext.Provider>
  )
}
나는 또한 갈고리를 만들어서 분파 함수를 더욱 명확하게 하는 것을 좋아한다.
export const useDispatch = () => {
  return useContext(DispatchContext)
}
기본적으로, 우리는 데이터와 조작을 분리한다. 공급자 구성 요소가 아이들에게 데이터를 제공한다.하위 레벨은 데이터를 수정할 수 있지만, 프로그램 구성 요소가 제어하기 때문에 데이터를 제어할 수 있습니다.스케줄링의 동작은dom 이벤트로 유사하게 이해할 수 있습니다. 누가 그것을 받아들일지 알지 않으면.

이제 REDUX를 대체하려면 대량의 구성 요소를 구독할 수 있는 큰 상태를 처리해야 한다.

불필요한 아동의 재창조를 피하다
이 구성에서는 상태의 특정 컨텐트를 변경할 때마다 모든 하위 객체가 다시 렌더링되기 때문에 효율성이 매우 낮습니다.공급자 구성 요소의 상태를 업데이트할 때마다 모든 하위 구성 요소가 다시 생성되기 때문입니다.우리는 반응을 할 수 있다.이러한 상황을 피하기 위해 더 좋은 해결 방안은 위의 구성 요소에서 하위 구성 요소를 전달하는 것이기 때문에 공급자가 업데이트할 때 하위 구성 요소는 변하지 않습니다.우리는 실제 상하문 소비자만 갱신한다.
export const Provider = ({ children }) => {

  ...

  return (
    <StateContext.Provider value={{state, ...}}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  )
}
학부모의 입장에서 우리는 다음과 같이 한다.
export const Parent = ({ children }) => {
  return (
    <Provider>
      <ChildComponent />
    </Provider>
  )
}
현재 공급자 구성 요소가 상하문을 관리하고 있지만 하위 단계만 관리하지 않습니다.나는 코드의 아주 작은 변경이기 때문에 매우 큰 결과를 가져올 수 있기 때문에 이런 미묘한 차이를 이해하는 데 시간이 걸렸다.
비결은 우리가 <ChildComponent >을 넣었을 때 기본적으로 새로운 반응을 창조하고 있다는 것을 이해하는 것이다.노드, 그래서 모든 하위 노드가 React.memo 에 포장되지 않는 한 다시 렌더링됩니다.
따라서 이 변경을 통해 위아래 문장을 사용하는 구성 요소만 업데이트합니다.

스케줄링으로 인한 재렌더링 방지
현재, 상태가 바뀔 때마다 디스패치 함수를 다시 생성합니다. 이것은 디스패치 함수를 사용하는 모든 구성 요소가 StateContext를 사용하지 않아도 다시 렌더링된다는 것을 의미합니다.보통 안정적인 함수reactuseCallback를 사용하고 싶지만, 이 경우, 기본적으로 스케줄링 함수의 '캐시' 를 초래하기 때문에 외부 범위 변수를 사용할 수 없고, 그것들을 dependencies에 포함하지 않고, 의존항이 변할 때 스케줄링 함수는 다시 만들어집니다.우리는 ref를 사용하여 우리가 이 문제를 해결하는 것을 도와야 한다.
...

export const Provider = ({ children }) => {
  const [state, setState] = useState(...)

  const dispatchRef = useRef()

  // new function with every render
  const dispatchRef.current = (action) => {
    switch (action.type) {
      case 'CHANGE_STATE':
        // we can use outer scope without restrictions
        setState({...action.payload, ...state})
        break;
      ...
    }
  }

  // stable dispatch function
  const dispatch = useCallback(
    (action: ActionType) => dispatchRef.current(action),
    [dispatchRef]
  );

  return (
    <StateContext.Provider value={{state, ...}}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  )
}
이런 방식을 통해 안정적인 분파 함수가 DispatchContext에 전달되고 우리는 외부 작용역을 제한 없이 사용할 수 있다.

아래 첨자 가능 컨텍스트
우리가 필요로 하는 마지막 최적화는 구성 요소가 일부 상태만 구독하는 능력이다.현재, 구성 요소는 전체 상태만 사용할 수 있습니다. 비록 작은 덩어리 (예를 들어 브리 값) 만 필요하더라도, 상태를 변경할 때마다 알림을 받을 수 있습니다.이것은 최선의 실천이 아니다. 왜냐하면 우리는 여전히 불필요한 재과장을 받을 수 있기 때문이다.이 문제를 해결하는 방법은 통과use-context-selector이다.
이 라이브러리는 매우 간단합니다. 선택기 기능을 사용하여 상태에서 우리가 원하는 것을 선택할 수 있습니다.
import { createContext } from 'use-context-selector';

const StateContext = createContext(null);

export const Provider = ({ children }) => {
  return (
    <StateContext.Provider value={{state, ...}}>
      <DispatchContext.Provider value={dispatch}>
        {children}
      </DispatchContext.Provider>
    </StateContext.Provider>
  )
}
import { useContextSelector } from 'use-context-selector';

export const Subscriber = () => {
  const somePart = useContextSelector(StateContext, context => context.somePart)
}
잠깐만, 그건 부정행위야!상하문 API만 사용한다고 했잖아!
이 라이브러리는 React의 간단한 포장입니다.상하문api.이것은 ref 패키지로 전달된 값을 사용하여 구성 요소가 자동으로 다시 렌더링되지 않고 구독 서버 목록을 보존할 수 있습니다.값이 변경되면 모든 구독 함수를 실행합니다. 선택기의 값이 이전과 다르면 구독 구성 요소를 다시 렌더링합니다.reduxuseSelectorhook에서 유사한 개념을 사용했다.그래서 나는 이것이 상당히 표준적인 해결 방안이라고 말한다. 이미 존재하는 이상 왜 새로운 해결 방안을 세워야 합니까?

PS: there is even a open RFC to add something like this directly into react



최종 제품
우리는 전체 기능을 다시 사용할 수 있도록 포장할 수 있다. (+ 유형 스크립트 유형 추가)
import React, { useCallback, useRef } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';

type DispatchType<ActionType, DispatchReturn> = (
  action: ActionType
) => DispatchReturn;

type SelectorType<StateType> = (state: StateType) => any;

export const createProvider = <
  StateType,
  ActionType,
  DispatchReturn,
  ProviderProps
>(
  body: (
    props: ProviderProps
  ) => [state: StateType, dispatch: DispatchType<ActionType, DispatchReturn>]
) => {
  const StateContext = createContext<StateType>(null as any);
  const DispatchContext = React.createContext<
    DispatchType<ActionType, DispatchReturn>
  >(null as any);

  const Provider: React.FC<ProviderProps> = ({ children, ...props }) => {
    const [state, _dispatch] = body(props as any);
    const dispatchRef = useRef(_dispatch);

    dispatchRef.current = _dispatch;

    // stable dispatch function
    const dispatch = useCallback(
      (action: ActionType) => dispatchRef.current?.(action),
      [dispatchRef]
    );

    return (
      <StateContext.Provider value={state}>
        <DispatchContext.Provider value={dispatch}>
          {children}
        </DispatchContext.Provider>
      </StateContext.Provider>
    );
  };

  const useDispatch = () => React.useContext(DispatchContext);
  const useStateContext = (selector: SelectorType<StateType>) =>
    useContextSelector(StateContext, selector);

  return [Provider, useDispatch, useStateContext] as const;
};
사용 예
type ActionType =
  | { type: 'CHANGE_STATE'; payload: ... }
  ...

export const [
  TranslationsContextProvider,
  useTranslationsDispatch,
  useTranslationsSelector,
] = createProvider(
  (props /* provider props */) => {
    const [state1, setState1] = useState(...)
    const [state2, setState2] = useState(...)
    const {data, isLoading} = useQuery(...)

    const dispatch = (action: ActionType) => {
      switch (action.type) {
        case 'CHANGE_STATE':
          setState(action.payload)
          break;
        ...
      }
    }

    const state = {
      state1,
      state2,
      data,
      isLoading
    }

    // don't forget to return state and dispatch function
    return [state, dispatch]
  })
이 솔루션의 이점을 요약해 보겠습니다.
  • 간단한 용법, 배울 만한 새로운 것이 없고, REDUX 등의 견본 문서가 없다
  • 단순히 상하문api
  • 를 사용하는 것보다 효율적이다
  • 갈고리의 모든 힘을 가지고 있을 때 신축
  • 여러 개의 실례를 사용할 수 있고 그 범위를 응용 프로그램에서 필요로 하는 부분에만 한정할 수 있다
  • 톨키에 있어요.io, 우리는 가장 복잡한 보기에서 그것을 사용합니다. 여기서 우리는 번역표를 처리합니다. 우리는 아직 아무런 문제도 겪지 않았습니다.
    당신은 어떻게 생각합니까?

    PS: Check Tolgee.io and give us github stars

    좋은 웹페이지 즐겨찾기