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);
