React & TypeScript:避免上下文默认值

IT技术 reactjs typescript react-hooks
2021-05-22 05:42:34

为了更好地学习 React、TypeScript 和 Context / Hooks,我正在制作一个简单的 Todo 应用程序。但是,使上下文所需的代码感觉很麻烦。

例如,如果我想改变一个 Todo 有什么,我必须在三个地方改变它(ITodo 接口、默认上下文值、默认状态值)。如果我想传递一些新的东西,我必须在三个地方(TodoContext、TodoContext 的默认值和 value=)这样做。有没有更好的方法来不必编写这么多代码?

import React from 'react'

export interface ITodo {
    title: string,
    body?: string,
    id: number,
    completed: boolean
}

interface TodoContext {
    todos: ITodo[],
    setTodos: React.Dispatch<React.SetStateAction<ITodo[]>>
}

export const TodoContext = React.createContext<TodoContext>({
    todos: [{title: 'loading', body: 'loading', id: 0, completed: false}],
    setTodos: () => {}
})

export const TodoContextProvider: React.FC<{}> = (props) => {
    const [todos, setTodos] = React.useState<ITodo[]>([{title: 'loading', body: 'loading', id: 0, completed: false}])

    return (
        <TodoContext.Provider value={{todos, setTodos}}>
            {props.children}
        </TodoContext.Provider>
    )
}
4个回答

没有办法避免声明接口和运行时值,因为 TS 的类型在运行时消失了,所以你只剩下运行时值。你不能从另一个生成一个。

但是,如果您知道您只会访问TodoContextProvider组件内的上下文,您可以TodoContext通过作弊来避免初始化,只需告诉 TS 您传递的内容是好的。

const TodoContext = React.createContext<TodoContext>({} as TodoContext)

如果您始终确保只访问TodoContextProviderwheretodossetTodoscreated with内部的上下文useState那么您可以安全地跳过TodoContext内部的初始化createContext因为该初始值实际上永远不会被访问。

react文档中的注意事项

仅当组件在树中没有匹配的 Provider 时才使用 defaultValue 参数。

我更喜欢这样做的方式是实际指定默认值可以是 undefined

const TodoContext = React.createContext<ITodoContext | undefined>(undefined)

然后,为了使用上下文,我创建了一个为我做检查的钩子:

function useTodoContext() {
  const context = useContext(TodoContext)
  if (context === undefined) {
    throw new Error("useTodoContext must be within TodoProvider")
  }

  return context
}

为什么我喜欢这种方法?它立即给我反馈为什么我的上下文值是undefined

如需进一步参考,请查看Kent C. Dodds 的这篇博文

过了一会儿,我想我已经找到了解决这个问题的最好方法。

import React from 'react'

export interface ITodo {
    title: string,
    body?: string,
    id: number,
    completed: boolean
}

const useValue = () => {
    const [todos, setTodos] = React.useState<ITodo[]>([])

    return {
        todos,
        setTodos
    }
}

export const TodoContext = React.createContext({} as ReturnType<typeof useValue>)

export const TodoContextProvider: React.FC<{}> = (props) => {
    return (
        <TodoContext.Provider value={useValue()}>
            {props.children}
        </TodoContext.Provider>
    )
}

这样,当向您的上下文添加新内容时,会有单点变化,而不是最初的三点变化。享受!

我的情况可能与您的情况略有不同(我意识到已经有一个公认的答案),但这目前似乎对我有用。从上面 Aron 的回答中修改,因为使用该技术在我的情况下实际上不起作用。

我的实际上下文的名称当然是不同的。

export const TodoContext = createContext<any>({} as any)