英文:
Using a method from the mobx store in React useMemo
问题
I have a MobX Store that contains user role information and a method that validates the role:
class Store {
constructor() {
makeAutoObservable(this)
}
role: string = ""
setRole(name: string) {
this.role = name
}
checkRole(name: string) {
return this.role === name
}
}
I want to use the checkRole
method inside useMemo
in my component, in order not to recalculate the value if the role has not changed in any way. (I realize that this example is a simple calculation and useMemo
is not needed, but it's just an example)
My component looks like this:
const App = observer(() => {
const [, forceRender] = useReducer((prev) => prev + 1, 0)
const store = useContext(StoreContext)
const isHaveAccess = useMemo(() => {
return store.checkRole('ADMIN')
}, [store, store.role])
return (
<div>
{isHaveAccess ? 'Access allowed' : 'Access denied'}
<button
onClick={() => store.setRole('ADMIN')}
>Set Role</button>
<button onClick={forceRender}>Force Render</button>
</div>
);
})
It works, but I get a warning from ESLint: React Hook useMemo has an unnecessary dependency: 'store.role'
, which says that store.role
is not used inside useMemo
.
Also, if I move the logic from the checkRole
method to useMemo
, the warning disappears.
Is this a normal practice or is there a better way to do it?
I tried using computed with arguments, but that didn't solve the problem, and the checkRole
method was called on every render.
英文:
I have a MobX Store that contains user role information and a method that validates the role:
class Store {
constructor() {
makeAutoObservable(this)
}
role: string = ""
setRole(name: string) {
this.role = name
}
checkRole(name: string) {
return this.role === name
}
}
I want to use the checkRole method inside useMemo in my component, in order not to recalculate the value if the role has not changed in any way. (I realize that this example is a simple calculation and useMemo is not needed, but it's just an example)
My component looks like this:
const App = observer(() => {
const [, forceRender] = useReducer((prev) => prev + 1, 0)
const store = useContext(StoreContext)
const isHaveAccess = useMemo(() => {
return store.checkRole('ADMIN')
}, [store, store.role])
return (
<div>
{isHaveAccess ? 'Access allowed' : 'Access denied'}
<button
onClick={() => store.setRole('ADMIN')}
>Set Role</button>
<button onClick={forceRender}>Force Render</button>
</div>
);
})
It works, but I get a warning from ESLint: React Hook useMemo has an unnecessary dependency: 'store.role'
, which says that store.role is not used inside useMemo.
Also, if I move the logic from the checkRole method to useMemo, the warning disappears.
Is this a normal practice or is there a better way to do it?
I tried using computed with arguments, but that didn't solve the problem and the checkRole method was called on every render.
答案1
得分: 1
你可以在这种情况下只是抑制这个eslint警告,保持代码不变。不足之处在于,这种方式下你的代码不太透明,基本上 useMemo
以某种方式知道了 checkRole
的内部实现,需要 store.role
来重新计算。想象一下,如果将来更改了实现方式,很容易忘记在memo调用中添加新的依赖项,或者移除多余的依赖项。这有点不太规范,但如果你可以接受它有点不太规范的话,这是更或多的正常做法。
我建议你尝试使用 mobx-utils
中的 computedFn,它基本上是你所需要的。不过要小心,它需要几乎是纯的,并且只依赖于可观察对象和函数参数。
// ...
checkRole = computedFn((name) => this.role === name)
// ...
另一个选择是将 checkRole
设计成纯函数,始终传递当前角色和目标角色,但这会显得有点冗长。不过 eslint
会比较满意。
英文:
You can just suppress this eslint warning in that case and keep the code as is. The downside is that your code is not very transparent that way, basically useMemo
somehow knows the internal implementation of checkRole
, that it needs store.role
to recalculate. Imagine that you change implementation in the future and it will be easy to forget to add new deps to the memo call, or remove extra. It's a bit dirty, but it's more or less normal practice if you are fine with it being dirty.
I would suggest you to try computedFn from mobx-utils
, it's basically exactly what you need. Be careful though, it needs to be almost pure and only rely on observables and function arguments.
//...
checkRole = computedFn((name) => this.role === name)
// ...
Another option is to make checkRole
pure and just pass both current and target role all the time, but it's a bit more verbose. eslint
will be happy though.
答案2
得分: 0
在一段时间后,我想出了另一个解决方案来解决这个问题。它包括在 useMemo
中使用 computed
。我们可以编写名为 useComputed
的钩子,它是对 useMemo
和 computed
的封装,看起来像这样:
const useComputed = <T>(factory: () => T, deps?: DependencyList): T => {
return useMemo(() => computed(factory), deps || []).get()
}
const App = observer(() => {
const [, forceRender] = useReducer((prev) => prev + 1, 0)
const store = useContext(StoreContext)
const isHaveAccess = useComputed(() => {
return store.checkRole('ADMIN')
}, [store])
return (
<div>
{isHaveAccess ? 'Access allowed' : 'Access denied'}
<button
onClick={() => store.setRole('ADMIN')}
>Set Right</button>
<button onClick={forceRender}>Force Render</button>
</div>
);
})
这是因为 useMemo
返回对 computed
函数的先前引用,因此在调用 get()
时,mobx 可以在观察到的数据未更改时返回缓存值。
英文:
After some time, I came up with another solution to the problem. It consists in using computed
inside useMemo
. We can write the hook useComputed
which is a wrapper over useMemo
and computed
, it looks like this:
const useComputed = <T,>(factory: () => T, deps?: DependencyList): T => {
return useMemo(() => computed(factory), deps || []).get()
}
const App = observer(() => {
const [, forceRender] = useReducer((prev) => prev + 1, 0)
const store = useContext(StoreContext)
const isHaveAccess = useComputed(() => {
return store.checkRole('ADMIN')
}, [store])
return (
<div>
{isHaveAccess ? 'Access allowed' : 'Access denied'}
<button
onClick={() => store.setRole('ADMIN')}
>Set Right</button>
<button onClick={forceRender}>Force Render</button>
</div>
);
})
This works because useMemo
returns a previous reference to computed function
, so when calling get()
, mobx can return a cached value if the observed data has not changed.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论