網站搜尋

reactjs - Typescript React Hooks 允許將上下文屬性設置為錯誤的類型


我對 React、React Hooks 和 JS/TS 還相當陌生。

目前我正在編寫一個簡單的按鈕,它通過 useContext 獲取一個狀態,並使用 useReducer 和 dispatch 函數更新該狀態。

我嘗試將我所有的程式碼分到特定的檔案中。來自 reducer 的 dispatch 函數作為值傳遞給 provider。

當我在消費者組件中調用傳遞的函數時,我可以給調度器傳入任何類型的值(在我的例子中是數字)。我在上下文、reducer 等中設置了所有類型。VSCode 告訴我它所期望的類型——看起來一切正確。但它仍然允許任何類型通過。為什麼會這樣?我嘗試在值被傳遞的所有點使用 console.log,似乎它用 number 類型覆蓋了類型,忽略了我指定的類型。

//app-state.tsx:
export type AppState = {
  readonly FoldoutPosition: number | null,
  readonly FoldoutType: FoldoutType | null
}

enum FoldoutType {
  Projects,
  Connection
}
//app-context.tsx
import { createContext, Dispatch } from 'react'
import { AppState} from '../states/app-state'

type ContextState =
  { AppState: AppState } &
  { changeFoldoutPosition: Dispatch<AppState["FoldoutPosition"]> } &
  { changeFoldoutType: Dispatch<AppState["FoldoutType"]> }


  export default createContext<ContextState>({
    AppState: {
      FoldoutType: null,
      FoldoutPosition: 0
    },
    changeFoldoutPosition: () => { },
    changeFoldoutType: () => { }
  });
//reducer.tsx
import { AppState } from '../states/app-state'


type Action =
    | { readonly type: 'CHANGE_FOLDOUTPOSITION', readonly payload: AppState["FoldoutPosition"] }
    | { readonly type: 'CHANGE_FOLDOUTTYPE', readonly payload: AppState["FoldoutType"] };


export const reducer = (state: AppState, action: Action): AppState => {

    switch (action.type) {
        case 'CHANGE_FOLDOUTPOSITION':
            return { ...state, FoldoutPosition: action.payload };
        case 'CHANGE_FOLDOUTTYPE':
                return { ...state, FoldoutType: action.payload };
        default:
            return state
    }

}
//wrapper.tsx
import React, { useReducer } from 'react'
import { reducer } from '../reducers/reducer'

import AppContext from './app-context'
import {AppState} from '../states/app-state'

const AppWrapper = (props: any) => {

  const initalState = {
    FoldoutType: null,
    FoldoutPosition: 15
  }

  const [state, dispatch] = useReducer(reducer, initalState);

  const changeFoldoutPosition = (FoldoutPosition: AppState["FoldoutPosition"]) => {
    dispatch({type: 'CHANGE_FOLDOUTPOSITION', payload: FoldoutPosition});
  }

  const changeFoldoutType = (FoldoutType: AppState["FoldoutType"]) => {
    dispatch({type: 'CHANGE_FOLDOUTTYPE', payload: FoldoutType});
  }

  return (
    <AppContext.Provider  
      value={{
        AppState: state,
        changeFoldoutPosition: changeFoldoutPosition,
        changeFoldoutType: changeFoldoutType
      }}
    >
      {props.children}
    </AppContext.Provider>
  );
}

export default AppWrapper;
/// component:
import React, { useContext } from 'react'
import AppContext from '../contexts/app-context'

const TestComponent = (props: any) => {

    const context = useContext(AppContext);

    return (
        //{context.AppState.Foldout}
        <div>
            <button onClick={() => context.changeFoldoutPosition(10)}>
                1 - {context.AppState.FoldoutPosition} - {context.AppState.FoldoutType}
                </button>
            // The following button onClick should not work
            // Type should be restricted to <FoldoutType | null>
            <button onClick={() => context.changeFoldoutType(23)}>
                2 - {context.AppState.FoldoutPosition} - {context.AppState.FoldoutType}
                </button>
        </div>
    );
}

export default TestComponent 

答案#1

這與 React 無關,這是純粹的 TypeScript 問題。TypeScript 中的枚舉有自動增量的功能,如果未指定,默認會使用數字初始化鍵。

來自文件:

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

在這裡,向上將有值 0,向下將有值 1,依此類推。

這就是為什麼你的 changeFoldoutType 允許傳遞一個整數作為參數:TypeScript 枚舉允許這樣做。

以你的例子來說,你的 TypeScript 代碼..

type AppState = {
  readonly FoldoutPosition: number | null,
  readonly FoldoutType: FoldoutType | null
}

enum FoldoutType {
  Projects,
  Connection
}

const testFn = (foldoutType: AppState["FoldoutType"]) => {
    console.log(foldoutType)
}

testFn(1)

console.log(FoldoutType)
// { 
//   "0": "Projects",
//   "1": "Connection",
//   "Projects": 0,
//   "Connection": 1
// }

..編譯成 JavaScript 如下,你可以清楚地看到用來存取 Enum 鍵的數字:

var FoldoutType;

(function (FoldoutType) {
    FoldoutType[FoldoutType["Projects"] = 0] = "Projects";
    FoldoutType[FoldoutType["Connection"] = 1] = "Connection";
})(FoldoutType || (FoldoutType = {}));

const testFn = (foldoutType) => {
    console.log(foldoutType);
};

testFn(1);

console.log(FoldoutType);