使用React中的useMemo从mobx存储中调用一个方法

huangapple go评论89阅读模式
英文:

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 = &quot;&quot;

    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(() =&gt; {
    const [, forceRender] = useReducer((prev) =&gt; prev + 1, 0)
    const store = useContext(StoreContext)

    const isHaveAccess = useMemo(() =&gt; {
        return store.checkRole(&#39;ADMIN&#39;)
    }, [store, store.role])

    return (
        &lt;div&gt;
            {isHaveAccess ? &#39;Access allowed&#39; : &#39;Access denied&#39;}
            &lt;button
                onClick={() =&gt; store.setRole(&#39;ADMIN&#39;)}
            &gt;Set Role&lt;/button&gt;
            &lt;button onClick={forceRender}&gt;Force Render&lt;/button&gt;
        &lt;/div&gt;
    );
})

It works, but I get a warning from ESLint: React Hook useMemo has an unnecessary dependency: &#39;store.role&#39;, 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) =&gt; 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 的钩子,它是对 useMemocomputed 的封装,看起来像这样:

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 = &lt;T,&gt;(factory: () =&gt; T, deps?: DependencyList): T =&gt; {
    return useMemo(() =&gt; computed(factory), deps || []).get()
}

const App = observer(() =&gt; {
    const [, forceRender] = useReducer((prev) =&gt; prev + 1, 0)
    const store = useContext(StoreContext)

    const isHaveAccess = useComputed(() =&gt; {
        return store.checkRole(&#39;ADMIN&#39;)
    }, [store])

    return (
        &lt;div&gt;
            {isHaveAccess ? &#39;Access allowed&#39; : &#39;Access denied&#39;}
            &lt;button
                onClick={() =&gt; store.setRole(&#39;ADMIN&#39;)}
            &gt;Set Right&lt;/button&gt;
            &lt;button onClick={forceRender}&gt;Force Render&lt;/button&gt;
        &lt;/div&gt;
    );
})

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.

huangapple
  • 本文由 发表于 2023年8月10日 18:51:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76875038.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定