Reactjs 通过useFooController/useFooHook限制使用useContext的组件的呈现
我正在为组件使用自定义挂钩,自定义挂钩使用自定义上下文。考虑Reactjs 通过useFooController/useFooHook限制使用useContext的组件的呈现,reactjs,react-hooks,use-context,Reactjs,React Hooks,Use Context,我正在为组件使用自定义挂钩,自定义挂钩使用自定义上下文。考虑 /*假设FooContext有{state:FooState,dispatch:()=>any}*/ const useFoo=()=>{ const{state,dispatch}=useContext(FooContextContext) 返回{apiCallable:()=>apiCall(状态)} } 常量Foo=()=>{ const{apiCallable}=useFoo() 返回( ) } 许多组件将从其他组件(表单输
/*假设FooContext有{state:FooState,dispatch:()=>any}*/
const useFoo=()=>{
const{state,dispatch}=useContext(FooContextContext)
返回{apiCallable:()=>apiCall(状态)}
}
常量Foo=()=>{
const{apiCallable}=useFoo()
返回(
)
}
许多组件将从其他组件(表单输入等)更改FooState
。在我看来,它就像Foo
使用useFoo
,它使用FooStateContext
中的state
。这是否意味着对FooContext
的每次更改都将重新呈现Foo
组件?它只需要在有人单击按钮时使用state,而不需要其他方式。看起来很浪费
我想useCallback
就是专门针对这个问题的,所以我想return{apiCallable:useCallback(()=>apiCall(state))}
但是我需要添加[state]
作为useCallback
的第二个参数。这意味着只要状态更新,回调就会被重新呈现,所以我又回到了同一个问题上,对吗
这是我第一次做这样的定制钩子。理解useCallback
有实际困难。我如何实现我想要的
编辑换句话说,我有很多组件,它们将向该状态的深层嵌套属性发送小更改,但是这个特定组件必须通过RESTful API发送整个状态对象,否则将永远不会使用该状态。这与完全渲染此组件无关。我想让这个组件永远不会呈现,即使我通过输入上的按键不断更改状态(例如)。既然您在问题中提供了Typescript类型,我将在回答中使用它们 方法一:分割上下文 给定以下类型的上下文:
type ItemContext = {
items: Item[];
addItem: (item: Item) => void;
removeItem: (index: number) => void;
}
您可以使用以下类型将上下文拆分为两个单独的上下文:
type ItemContext = Item[];
type ItemActionContext = {
addItem: (item: Item) => void;
removeItem: (index: number) => void;
}
然后,提供组件将处理这两个上下文之间的交互:
const ItemContextProvider = () => {
const [items, setItems] = useState([]);
const actions = useMemo(() => {
return {
addItem: (item: Item) => {
setItems(currentItems => [...currentItems, item]);
},
removeItem: (index: number) => {
setItems(currentItems => currentItems.filter((item, i) => index === i));
}
};
}, [setItems]);
return (
<ItemActionContext.Provider value={actions}>
<ItemContext.Provider value={items}>
{children}
</ItemContext.Provider>
</ItemActionContext.Provider>
)
};
这将允许您使用选择器函数(如React-Redux的极其基本的版本useSelector
)减少重新渲染,因为可下标值(根对象)在其生命周期内永远不会更改引用
这样做的缺点是,您必须管理订阅,并始终使用set
功能更新保留值,以确保订阅将得到通知
结论:
不同的人可能会有很多其他的方法来解决这个问题,你必须找到一个适合你的问题
如果您的上下文/状态需求范围更大,那么第三方库(如Redux)也可以帮助您实现这一点
这是否意味着对FooContext的每次更改都将重新呈现Foo组件
目前(v17
),上下文API没有紧急援助。检查我的电脑。因此,是的,它将始终在上下文更改时重新播放
它只需要在有人单击按钮时使用state,而不需要其他方式。看起来很浪费
可以通过拆分上下文提供程序来修复此问题,请参见上面的相同答案以获取解释。这是react-redux在尝试提前采用react-context来执行存储状态时遇到的问题。钩子不够聪明,无法知道您正在使用的上下文的哪些部分,因此,如果上下文的任何部分发生更改,则必须重新启动组件。如果上下文不太大,则可以考虑将其拆分为两个,一个用于上下文的上下文,另一个是用于更改状态的上下文。通过这种方式,提供组件可以处理这两个上下文的交互方式,并且您可以在不重新引发的情况下执行操作。您是否尝试过使用React.memo来记忆
Foo
组件?
type Subscription<T> = (val: T) => void;
type Unsubscribe = () => void;
class SubscribableValue<T> {
private subscriptions: Subscription<T>[] = [];
private value: T;
constructor(val: T) {
this.value = val;
this.get = this.get.bind(this);
this.set = this.set.bind(this);
this.subscribe = this.subscribe.bind(this);
}
public get(): T {
return this._val;
}
public set(val: T) {
if (this.value !== val) {
this.value = val;
this.subscriptions.forEach(s => {
s(val)
});
}
}
public subscribe(subscription: Subscription<T>): Unsubscriber {
this.subscriptions.push(subscription);
return () => {
this.subscriptions = this.subscriptions.filter(s => s !== subscription);
};
}
}
type ItemContext = SubscribableValue<Item[]>;
const ItemContextProvider = () => {
const subscribableValue = useMemo(() => new SubscribableValue<Item[]>([]), []);
return (
<ItemContext.Provider value={subscribableValue}>
{children}
</ItemContext.Provider>
)
};
// Get access to actions to add or remove an item.
const useItemContextActions = () => {
const subscribableValue = useContext(ItemContext);
const addItem = (item: Item) => subscribableValue.set([...subscribableValue.get(), item]);
const removeItem = (index: number) => subscribableValue.set(subscribableValue.get().filter((item, i) => i === index));
return {
addItem,
removeItem
}
}
type Selector = (items: Item[]) => any;
// get access to data stored in the subscribable value.
// can provide a selector which will check if the value has change each "set"
// action before updating the state.
const useItemContextValue = (selector: Selector) => {
const subscribableValue = useContext(ItemContext);
const selectorRef = useRef(selector ?? (items: Item[]) => items)
const [value, setValue] = useState(selectorRef.current(subscribableValue.get()));
const useEffect(() => {
const unsubscribe = subscribableValue.subscribe(items => {
const newValue = selectorRef.current(items);
if (newValue !== value) {
setValue(newValue);
}
})
return () => {
unsubscribe();
};
}, [value, selectorRef, setValue]);
return value;
}