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

tzcvj98z  于 2023-11-20  发布在  TypeScript
关注(0)|答案(5)|浏览(175)

为了更好地学习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>
    )
}

字符串

91zkwejq

91zkwejq1#

没有办法避免声明接口和运行时值,因为TS的类型在运行时消失,所以你只剩下运行时值。你不能从另一个生成一个。
但是,如果你知道你只会访问TodoContextProvider组件中的上下文,你可以通过欺骗一点来避免初始化TodoContext,只告诉TS你传递的内容是好的。

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

字符串
如果你总是确保只访问TodoContextProvider内部的上下文,其中todossetTodos是用useState创建的,那么你可以安全地跳过在createContext内部初始化TodoContext,因为初始值永远不会被访问。

ahy6op9u

ahy6op9u2#

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的这篇博客文章

y1aodyip

y1aodyip3#

一段时间后,我想我找到了最好的方法。

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

字符串
这样,当你在上下文中添加新的东西时,只有一个变化点,而不是最初的三个变化点。

jq6vz3qz

jq6vz3qz4#

我的情况可能与你的情况有点不同(我意识到已经有一个公认的答案),但这似乎对我现在有效。修改自Aron的答案,因为使用该技术实际上并不适用于我的情况。
当然,我的实际上下文的名称是不同的。
第一个月

suzh9iv8

suzh9iv85#

我认为@marinvirdol的答案很棒,我想用一个通用的 Package 器来扩展这个答案。

import { useContext as _useContext, createContext as _createContext } from 'react'

// Wrapper functions
export function useContext<T>(context: React.Context<T>) {
    const value = _useContext(context)
    if (value === undefined) throw new Error('context must be within provider')
    return value
}

export function createContext<T>(value: T | undefined = undefined) {
    return _createContext<T | undefined>(undefined)
}

// Define context like so:
const SomeContext = createContext<SomeObjectType>()

// Use like before (but make sure to import the wrappers instead of the react one)
const someValue = useContext(SomeContext); // No longer optional!

字符串

相关问题