首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >乐观渲染与useEffect

乐观渲染与useEffect
EN

Stack Overflow用户
提问于 2019-08-14 22:01:32
回答 1查看 1.2K关注 0票数 5

TL‘’DR:是否有一种方法可以在使用useEffect进行API调用时悲观地操作状态更改?

假设您编写了一个显示分页/排序数据网格的组件,并且为该组件编写了一个useEffect,类似于这个假示例:

代码语言:javascript
运行
复制
useEffect(() => {
  fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
    .then(response => response.json())
    .then(data => setState({
      data
    }));
}, [paging, sorting]);

因此,如果我没有弄错,这将在更新分页或排序状态/支持(whatev)时获取。这意味着它在加载数据之前乐观地呈现新的排序和分页状态。基本上,该应用程序告诉用户“我在请求的页面上有请求的排序,但我仍然在加载它”。换句话说,应用程序状态(分页和排序)的一部分表示“我完成了”,而状态的另一部分(加载)则表示“我正在处理”。

相反,如果我要悲观地更新状态,我只会在获取之前将加载状态设置为true,然后当接收到响应时(然后才)设置分页、排序(& data & would )。因此,该应用程序告诉用户“我听到了,我正在做它”,然后当收到一个成功的响应时,应用程序说“这是请求的数据和更新的上下文(更新的页面/排序状态)”。

另一个问题是请求是否失败。在乐观/useEffect方法中,我现在需要在尝试加载这些状态更改的数据之前,恢复已更新的分页和排序状态。这种复杂性随着依赖项的数量而增加。另外,这里还有一个问题(如果我没有弄错的话),恢复这些状态将导致另一次获取(这可能再次失败)。在悲观方法中,您只需在发生错误时设置made = false,并且不会进行其他状态更改,因为分页和排序只有在成功接收到数据时才会更新。数据以悲观的方式(不是乐观的)保持同步,这就是为什么我认为当博客和视频在使用useEffect时说“从同步的角度思考”是很讽刺的。

还有一件事,比如从useEffect内部调用的API (在其他例子中)实际上是在服务器端改变一些东西。在这种情况下,当使用useEffect并应用它的乐观方法时,只是在欺骗用户。这个应用程序是说“好吧,我更新了”。也许不是。如果没有(如果有错误),希望有状态更新来恢复对用户撒谎的状态更改,并在恢复状态时避免额外的API调用。希望用户看到状态的改变&希望有一条信息.即使如此,这是一个好的UX吗?

我不是说乐观渲染总是不好的。我说的是“我大多数时候都喜欢悲观,我如何在使用useEffect时做到这一点?”)

useEffect似乎正在被应用程序的大多数副作用所使用和推广(很多)。我不觉得它经常有用。我是个悲观主义者,希望能有其他选择。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2019-08-14 22:39:34

按照您描述它的方式,听起来您的组件的其余部分的结构类似于

代码语言:javascript
运行
复制
[state, setState] => useState(initialState)
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)

useEffect(() => {
  fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
    .then(response => response.json())
    .then(data => setState({
      data
    }));
}, [paging, sorting]);

return <div>
    <span>Current page is {paging}</span>
    <Button onClick={() => setPaging(page => page + 1)}>Nav Next</Button>
    <Button onClick={() => setPaging(page => page - 1)}>Nav Prev</Button>
    // ... and so on and so forth
</div>

当您单击Next或Prev时,您不希望在其触发的效果得到解决之前更新分页。如果提取失败,您不希望用户看到分页向上移动,然后从需要进行的任何清理中下降。

要做到这一点,你不能通过中间的“希望”或“待定”状态值来延迟设置“前向”值吗?例如,这样做有效吗?

代码语言:javascript
运行
复制
[state, setState] => useState(initialState)

// Component state values
[paging, setPaging] => useState(initialPaging)
[sorting, setSorting] => useState(initialSort)

// "Optimist's" state values
[pendingPaging, setPendingPaging] => useState(paging)
[pendingSorting, setPendingSorting] => useState(sorting)

useEffect(() => {
  // checks if current state values differ from pending,
  // meaning app wants to do something
  // If pending and "actual" values match, do nothing
  if( paging !== pendingPaging
    || sorting !== pendingSorting
  ){
      fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`)
        .then(response => response.json())
        .then(data => {
          // things worked, updated component state to match pending
          setPaging(pendingPaging);
          setSorting(pendingSorting);
          setState({data});
        })
        .catch(err => {
          // ugh, I knew things would fail. Let me reset my hopes
          setPendingPaging(paging);
          setpendingSorting(sorting);
          setState({data});
        });
  }
}, [pendingPaging, pendingSorting, paging, sorting, setState]);

return <div>

    // show user "actual" page number
    <span>Current page is {paging}</span>

    // but update only pending values in response to events
    <Button onClick={() => setPendingPaging(page + 1)}>Nav Next</Button>
    <Button onClick={() => setPendingPaging(page - 1)}>Nav Prev</Button>

    // ... and so on and so forth
</div>

这样,您就可以建立您希望的状态,做一些事情,然后设置您的状态和期望与之匹配。如果成功,则显示更新以匹配乐观值的状态,如果失败,则将挂起的值设置为实值。

我看到的最大问题是,异步调用解决时同时执行多个setState调用。React应该是批处理这些呼叫,但是您可能希望在单个调用中查找处理此问题的其他方法。

无论哪种方式,挂起值和实际值都是相等的,所以虽然useEffect将触发,但它不会运行另一个fetch。

编辑1谢谢你的积极反馈,很高兴你喜欢这个概念。我同意复杂性可能会变得难以处理,但我认为你可以建立一个工厂类来更清楚地管理事情。

例如,您可以使用一个makePessimistic类,它最终输出“实际”状态、“希望”状态和至少一个usePessimisticEffect回调。这门课看起来可能是:

代码语言:javascript
运行
复制
class pessimistFactory {
  constructor(){
    this.state = {};
    this.effects = [];
  }
  // util from https://dzone.com/articles/how-to-capitalize-the-first-letter-of-a-string-in
  ucfirst = (string) => string.charAt(0).toUpperCase() + string.slice(1);

  registerState(param, initialVal){
    this.state[param] = initialVal;
  }

  makePessimisticEffect = (callback, dependencies) => useEffect(() => {
    async function handlePessimistically(){
      try {
        const result = await callback(...dependencies);
        // update theState with result
        setState({...hopefulState, result})
      }catch(err){
        // reset hopefulState and handle error
        setHopefulState({...theState})
      }
    }

    // Do something if some action pending
    if(/* pending and current state not the same */) handlePessimistically();

  }, [callback, dependencies]);

  registerEffect(callback, dependencies = []){
    this.effects.push(
      this.makePessimisticEffect(callback, dependencies)
    );
  }

  makeHooks = () => {
    const initialState = useState(this.state);
    return {
      theState: initialState,
      hopefulState: initialState,
      effects: this.effects
    }
  }
}

在实践中:

代码语言:javascript
运行
复制
// Make factory instance
const factory = new pessimistFactory();

// Register state and effects
factory.registerState('paging', initialPaging)
factory.registerState('sorting', initialSorting)
factory.registerEffect('fetchData', () => fetch(`https://some-random-api.com/data/page/{paging}/sort/{sorting}`))

// Make the hooks
const {theState, hopefulState, effects} = factory.makeHooks();

// Implement hooks in component
const SomeComponent = () => {
  const [state, setState] = theState();
  const [pendingState, setPendingState] = hopefulState();

  effects.forEach(useEffect => useEffect());

  return <div>displayed stuff</div>
}

最棘手的部分是设置makePessimisticEffect效果,以读取和处理由生成的setState创建的值和回调,如果在编写时完全破坏了这些值和回调,我就会意识到这一点。

我现在没有时间来真正弄清楚细节,但我认为这必须是可能的新挂钩,如useCallback

我确实很喜欢这个想法,所以我会在有时间的时候试着解决这个问题。如果你在那之前做了一个工人阶级,或者有人有一个完全不同的更好的解决方案,我会非常感兴趣的。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57502612

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档