ice-lab / icestore

🌲 Simple and friendly state for React
MIT License
397 stars 35 forks source link

如何实现调用 effects 失败后弹一个 toast #138

Closed imsobear closed 2 years ago

imsobear commented 3 years ago

Problem Description

// models/a.ts
export default {
  state: {},
  reducers: {},
  effects: (dispatch) => ({
    async test() { return Promise.reject(new Error('错误')); }
  }),
};

function Home() {
  const [state, dispatchers] = store.useModel('a');
  const effectsState = store.useModelEffectsState('a');

  useEffect(() => {
    dispatchers.test().then(() => {
      toast.success();
    }).catch(() => {
      toast.error();
    })
  }, [dispatchers]);
}

如上代码,因为 icestore 默认支持了 effectsState 能力,该能力会主动 try/catch effects 以拿到 error 状态直接提供给组件消费,因此调用端无法再对 effects 进行 catch,从合理性来讲这个实现是符合预期的。

https://github.com/ice-lab/icestore/blob/de76ab8d1113b78262ea0db80550daa5445424b2/src/plugins/error.tsx#L210-L216

目前想要满足这个诉求的话,需要在 createStore() 的地方通过 disableError 的选项禁用内置 try/catch effect 的功能,但是这个方案影响面过大,会导致这个 store 里的所有 models 都没法使用内置提供的 error/loading 状态。

Proposed Solution

  1. useModel 支持 disableError/disableLoading 配置项(不确定是否能实现),这样当开发者需要主动 catch 的时候在对应的地方 disableError
    • 有个问题是 catch 的时候就不能用内置的 error 状态了,不过我感觉是合理的,这种情况想用就自己 setState
  2. useModel 支持 onError onSuccess 的配置项,也能满足诉求
  3. 类似 ahooks 3.0 单独提供一个 runAsync 方法,icestore 的场景感觉不太可取 https://github.com/alibaba/hooks/issues/1173#issuecomment-929775009

Additional Information

参考了 ahooks/useRequest 的实现,有一个 throwOnError 的选项,开启之后错误就会抛出并且不影响原先的 error 状态,不过方案整体也有一些 bug https://github.com/alibaba/hooks/issues/812 ,ahooks 这里的实现比较复杂,虽然看起来比较强大,但不合理的地方也挺多。

luhc228 commented 3 years ago

感觉这个诉求可以不用满足。诸如弹窗这类的带有副作用的行为,应该根据 state(上面例子中则是 error) 的改变而控制是否发出该行为。

luhc228 commented 3 years ago

要是上面4个方案来说,throwOnError 会更好点。

imsobear commented 3 years ago

感觉这个诉求可以不用满足。诸如弹窗这类的带有副作用的行为,应该根据 state(上面例子中则是 error) 的改变而控制是否发出该行为。

社区里的库有类似做法吗?大概写法是下面这样:

function Home() {
  const [state, dispachers] = store.useModel('user');
  const userEffectsState = store.useModelEffectsState('user');

  useEffect(() => {
    dispachers.fetchUser();
  }, [])

  useEffect(() => {
    if (userEffectsState.fetchUser.error) { alert('fetchUser 出错'); }
  }, [userEffectsState.fetchUser.error]);

  return <></>;
}
luhc228 commented 3 years ago

https://reactjs.bootcss.com/docs/hooks-effect.html#tip-use-multiple-effects-to-separate-concerns 上面写的例子很符合上面文档所说的关注点分离~

imsobear commented 2 years ago

https://reactjs.bootcss.com/docs/hooks-effect.html#tip-use-multiple-effects-to-separate-concerns 上面写的例子很符合上面文档所说的关注点分离~

感觉不是一个意思,那个强调的还是类似订阅/事件监听的场景,使用 useEffect 绑定和解绑可以成对出现,代码可读性更高。

alvinhui commented 2 years ago

感觉这个诉求可以不用满足。诸如弹窗这类的带有副作用的行为,应该根据 state(上面例子中则是 error) 的改变而控制是否发出该行为。

社区里的库有类似做法吗?大概写法是下面这样:

function Home() {
  const [state, dispachers] = store.useModel('user');
  const userEffectsState = store.useModelEffectsState('user');

  useEffect(() => {
    dispachers.fetchUser();
  }, [])

  useEffect(() => {
    if (userEffectsState.fetchUser.error) { alert('fetchUser 出错'); }
  }, [userEffectsState.fetchUser.error]);

  return <></>;
}

这个写法感觉没什么问题,error 本身也是一个 state

imsobear commented 2 years ago

@alvinhui 是没什么问题,但是很多业务开发的同学可能没这个直觉。

imsobear commented 2 years ago

https://beta-reactjs-org-git-you-might-not-fbopensource.vercel.app/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers @alvinhui @luhc228 可以看下这篇文章,按这篇文章的逻辑这个场景用 useEffect 仿佛是不合理的。