美文网首页
Recoil学习(三)---异步、同步

Recoil学习(三)---异步、同步

作者: koala949 | 来源:发表于2020-06-22 16:33 被阅读0次

Recoil通过数据流图将数据及衍生数据映射给组件,其真正强大之处是可异步,我们可以react组件中使用异步函数。Recoil允许我们在数据流图的seletors中无缝混合使用同步和异步函数。从selector 的get回调中返回Promise对象,接口保持不变。

selectors可以用作将异步数据合并到recoil数据流图中的一种方法。记住selectors是纯函数:对于给定的一组输入,它们应该总是产生相同的结果。这一点非常重要,因为选择器计算可能会执行一次或多次,可能会重新启动,也可能会被缓存。因此,selectors是建模只读DB查询的好方法,这重复查询可以提供一致的数据。如果您想同步本地和服务器状态,那么请参阅异步状态同步状态持久

只读数据

同步举例

一个简单的同步 atom 和 selector 来获取user name.

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: 1,
});

const currentUserNameState = selector({
  key: 'CurrentUserName',
  get: ({get}) => {
    return tableOfUsers[get(currentUserIDState)].name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameState);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <CurrentUserInfo />
    </RecoilRoot>
  );
}

异步举例

如果用户名存储在我们需要查询的数据库中,我们所需要做的就是返回一个Promise或使用异步函数。当依赖变化时 selector会被重新评估并执行新的查询,查询结果会被缓存,因此对于每个唯一的输入,查询只执行一次。

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

selector的接口是相同的,因此使用此selector的组件不需要关心它是由同步原子状态、派生选择器状态还是异步查询支持的。

但是,由于React呈现函数是同步的,所以在Promise解析之前它会呈现什么?Recoil设计与React Suspense一起处理待处理数据。用Suspense 边界包装组件,将捕获任何仍然挂起的派生组件,并回退给UI呈现。

function MyApp() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <CurrentUserInfo />
      </React.Suspense>
    </RecoilRoot>
  );
}

错误处理 <ErrorBoundary />

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

带参查询

有时希望能够基于参数进行查询,而不仅仅是基于派生状态。例如,可能想要基于组件props进行查询。你以使用selectorFamily helper来完成:

const userNameQuery = selectorFamily({
  key: 'UserName',
  get: userID => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function UserInfo({userID}) {
  const userName = useRecoilValue(userNameQuery(userID));
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <UserInfo userID={1}/>
          <UserInfo userID={2}/>
          <UserInfo userID={3}/>
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

数据流图

当状态更新时,数据流图将自动更新并重新呈现React组件。
下面的示例将呈现当前用户的名称及其好友列表。如果一个朋友的名字被点击,他们将成为当前的用户,名字和列表将自动更新。

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: null,
});

const userInfoQuery = selectorFamily({
  key: 'UserInfoQuery',
  get: userID => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response;
  },
});

const currentUserInfoQuery = selector({
  key: 'CurrentUserInfoQuery',
  get: ({get}) => get(userInfoQuery(get(currentUserIDState))),
});

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = [];
    for (const friendID of friendList) {
      const friendInfo = get(userInfoQuery(friendID));
      friends.push(friendInfo);
    }
    return friends;
  },
});

function CurrentUserInfo() {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);
  const setCurrentUserID = useSetRecoilState(currentUserIDState);
  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => setCurrentUserID(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

在上面的示例中,friendsInfoQuery使用一个查询来获取每个朋友的信息。但是,通过在循环中这样做,它们本质上是序列化的。如果查找速度快,也许没问题,如果开销较大,您可以使用并发helper程序,如waitForAll、waitForNone或waitForAny,以并行地运行它们或处理部分结果,它们接受数组和命名对象作为依赖。

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(waitForAll(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friends;
  },
});

Pre-Fetching

出于性能考虑,您可能希望在呈现之前获取数据,这样查询就可以在我们开始渲染的时候进行。React文档给出了一些例子,这种模式也适用于Recoil。
我们改变上面的例子,当用户点击改变用户的按钮时,就开始获取下一个用户信息:

function CurrentUserInfo() {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);

  const changeUser = useRecoilCallback(({snapshot, set}) => userID => {
    snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
    set(currentUserIDState, userID); // change current user to start new render
  });

  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => changeUser(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  );
}

不使用React Suspense

没有必要使用React Suspense来处理挂起的异步选择器。你也可以使用useRecoilValueLoadable()钩子来确定渲染期间的状态。

function UserInfo({userID}) {
  const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
  switch (userNameLoadable.state) {
    case 'hasValue':
      return <div>{userNameLoadable.contents}</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw userNameLoadable.contents;
  }
}

读写数据

与服务器同步状态

我们可以订阅远程状态中的异步更改,并更新atom值以匹配。
可以在react useEffect 实现。

function CurrentUserIDSubscription() {
  const setCurrentUserID = useSetRecoilState(currentUserIDState);

  useEffect(() => {
    RemoteStateAPI.subscribeToCurrentUserID(setCurrentUserID);
    // Specify how to cleanup after this effect
    return function cleanup() {
      RemoteServerAPI.unsubscribeFromCurrentUserID(setCurrentUserID);
    };
  }, []);

  return null;
}

function MyApp() {
  return (
    <RecoilRoot>
      <CurrentUserIDSubscription />
      <CurrentUserInfo />
    </RecoilRoot>
  );
}

双向同步

您还可以同步状态,以便在服务器上更新本地更改。注意,这是一个简化的示例,请注意避免反馈循环。

function CurrentUserIDSubscription() {
  const [currentUserID, setCurrentUserID] = useRecoilState(currentUserIDState);
  const knownServerCurrentUserID = useRef(currentUserID);

  // Subscribe server changes to update atom state
  useEffect(() => {
    function handleUserChange(id) {
      knownServerCurrentUserID.current = id;
      setCurrentUserID(id);
    }

    RemoteStateAPI.subscribeToCurrentUserID(handleUserChange);
    // Specify how to cleanup after this effect
    return function cleanup() {
      RemoteServerAPI.unsubscribeFromCurrentUserID(handleUserChange);
    };
  }, [knownServerCurrentUserID]);

  // Subscribe atom changes to update server state
  useEffect(() => {
    if (currentUserID !== knownServerCurrentUserID.current) {
      knownServerCurrentID.current = currentUserID;
      RemoteServerAPI.updateCurrentUser(currentUserID);
    }
  }, [currentUserID, knownServerCurrentUserID.current]);

  return null;
}

带参状态同步

还可以使用atomFamily helper根据参数同步本地状态。注意,这个示例钩子的每次调用都将创建一个订阅,因此要注意避免冗余使用。

const friendStatusState = atomFamily({
  key: 'Friend Status',
  default: 'offline',
});

function useFriendStatusSubscription(id) {
  const setStatus = useSetRecoilState(friendStatusState(id));

  useEffect(() => {
    RemoteStateAPI.subscribeToFriendStatus(id, setStatus);
    // Specify how to cleanup after this effect
    return function cleanup() {
      RemoteServerAPI.unsubscribeFromFriendStatus(id, setStatus);
    };
  }, []);
}

数据流图

使用原子来表示远态的一个优点是,你可以使用它作为其他导出态的输入。下面的示例将显示基于当前服务器状态的当前用户和好友列表。如果服务器改变了当前用户,它将重新呈现整个列表,如果它只改变了一个朋友的状态,那么只有该列表条目将被重新呈现。如果单击列表项,它将在本地更改当前用户并更新服务器状态。

const userInfoQuery = selectorFamily({
  key: 'UserInfoQuery',
  get: userID => async ({get}) => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response;
  },
});

const currentUserInfoQuery = selector({
  key: 'CurrentUserInfoQuery',
  get: ({get}) => get(userInfoQuery(get(currentUserIDState)),
});

const friendColorState = selectorFamily({
  key: 'FriendColor',
  get: friendID => ({get}) => {
    const [status] = useRecoilState(friendStatusState(friendID));
    return status === 'offline' ? 'red' : 'green';
  }
})

function FriendStatus({friendID}) {
  useFriendStatusSubscription(friendID);
  const [status] = useRecoilState(friendStatusState(friendID));
  const [color] = useRecoilState(friendColorState(friendID));
  const [friend] = useRecoilState(userInfoQuery(friendID));
  return (
    <div style={{color}}>
      Name: {friend.name}
      Status: {status}
    </div>
  );
}

function CurrentUserInfo() {
  const {name, friendList} = useRecoilValue(currentUserInfoQuery)
  const setCurrentUserID = useSetRecoilState(currentUserIDState);
  return (
    <div>
      <h1>{name}</h1>
      <ul>
        {friendList.map(friendID =>
          <li key={friend.id} onClick={() => setCurrentUserID(friend.id)}>
            <React.Suspense fallback={<div>Loading...</div>}>
              <FriendStatus friendID={friendID} />
            </React.Suspense>
          </li>
        )}
      </ul>
    </div>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserIDSubscription />
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

相关文章

  • Recoil学习(三)---异步、同步

    Recoil通过数据流图将数据及衍生数据映射给组件,其真正强大之处是可异步,我们可以react组件中使用异步函数。...

  • 2018-05-24-多线程学习

    java多线程并发的编程学习 1,概念的学习 同步异步:同步需要等待异步不需要,同步如对共享数据操作避免脏数据...

  • UNIX 的5种IO模型介绍

    IO模型同步、异步、阻塞、非阻塞socket阻塞与非阻塞,同步与异步 同步和异步 同步/异步主要针对C端-同步就像...

  • I/O模型

    在学习I/O模型前,我们首先介绍同步和异步、阻塞和非阻塞的概念 1. 同步和异步 同步和异步是针对应用程序和内核的...

  • 网络请求数据并且解析的过程(推荐AFNetworking)

    demo1.http请求,get同步异步,post同步异步get同步 get异步 post异步 2.session...

  • GCD队列、同步异步

    GCD队列、同步异步 GCD队列、同步异步

  • 存储

    同步 Set 异步 Get 同步 Get 异步 Get

  • 前台数据传输

    数据提交 同步提交 方式一: 方式二: 方式三: 方式四: 异步提交 方式一: 方式二: 异步和&同步比较 for...

  • GCD的几种创建方式及基本使用

    同步函数 同步函数+主队列 同步函数+串行队列 同步函数+并发队列 异步函数 异步函数+主队列 异步函数+串行队列...

  • GCD基础总结一

    上代码~ 同步串行队列 同步并行队列 异步串行队列 异步并行队列 主队列同步 会卡住 主队列异步

网友评论

      本文标题:Recoil学习(三)---异步、同步

      本文链接:https://www.haomeiwen.com/subject/iuacxktx.html