美文网首页Front End
[FE] Hello "Observable Hooks"

[FE] Hello "Observable Hooks"

作者: 何幻 | 来源:发表于2021-03-24 08:25 被阅读0次

最近项目中用到了 react + rxjs + observable-hooks,下文总结一下 observable-hooks 的学习心得。

1. 搭建 React 环境

使用 create-react-app 创建 react 项目 test-hooks

$ npm i -g create-react-app
$ create-react-app test-hooks
$ cd test-hooks
$ npm i
$ npm start

我们可以在 src/App.js 中写一些实验代码,例如,

function App() {
  return (
    <>Hello World</>
  );
}
export default App;

2. Observable Hooks

Observable Hooks 是 React hooks for RxJS Observables
官方文档 Core Concepts 中介绍了 Two Worlds 概念模型,非常值得一读。

为了使用 Observable Hooks,需要在上文 test-hooks 项目中,
安装 rxjsobservable-hooks 依赖。

$ npm i -S rxjs observable-hooks

(1)异步取值

功能:页面初始化展示 0,500ms 后展示 1

import { from } from 'rxjs';
import { useObservableState } from 'observable-hooks';

function App() {
  const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
  const initialOutput = 0;
  const output = useObservableState(output$, initialOutput);

  return (
    <>{output}</>
  );
}
export default App;
  • from 将 Promise 转换成了一个 rxjs 流(stream)output$
  • useObservableState 会订阅(subscribe)这个流,流中每一个值 “到来” 的时候,更新 output
  • output 的初始值为 initialOutput,总是先于流中的第一个值被赋值

这种写法非常适合处理异步加载页面的场景:

首屏加载后,前端通过 ajax 请求获取数据,更新页面。

(2)事件更新

功能:页面展示一个按钮和数字 0,每次点击按钮,后面的数字加一。

import { useObservableState } from 'observable-hooks';

function App() {
  const initialOutput = 0;
  const transform = output$ => output$.pipe(
    //
  );
  const [output, onInput] = useObservableState(transform, initialOutput);

  const onClick = () => {
    onInput(output + 1);
  };

  return (
    <>
      <input type="button" value="Add One" onClick={onClick} />
      {output}
    </>
  );
}
export default App;
  • output 的初始值被设置为 initialOutput
  • 每次调用 onInput 就会向 output$ 流中加入一个新值
  • transform 是流变换函数,可以用 rxjs pipe + operators 来实现
  • useObservableState 会订阅 transform 过后的新流,来对 output 重新赋值

每次 onInput 加入流中的值,
通过 transform,再经过 useObservableState,最后赋值给了 output

(3)异步回调

功能:页面加载 500ms 后,写 log

import { from } from 'rxjs';
import { useSubscription } from 'observable-hooks';

function App() {
  const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
  useSubscription(output$, v => {
    console.log(v);
  });

  return (
    <>Hello World</>
  );
}
export default App;
  • from 会将 Promise 转换成一个流 output$
  • 流中每一个值 “到来” 的时候,触发回调
  • useSubscription 可以多次使用,流中的每一个值会触发多次回调
const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
useSubscription(output$, v => {
  console.log(v);  // 1
});
useSubscription(output$, v => {
  console.log(v);  // 1
});

实际上,上文介绍的 useObservableStateuseSubscription 对于同一个流也是可以多次使用的。

import { from } from 'rxjs';
import { useObservableState, useSubscription } from 'observable-hooks';

function App() {
  const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));

  const initialOutput = 0;
  const output = useObservableState(output$, initialOutput);
  debugger;  // 1 [先触发,跟先后顺序有关]
  
  useSubscription(output$, v => {
    debugger;  // 1 [后触发,跟先后顺序有关,放在前面就会先触发]
  });

  return (
    <>Hello World</>
  );
}
export default App;

(4)变量写入流

功能:
页面展示一个按钮和数字 0,每次点击按钮,后面的数字加一。
按钮点击会改变组件状态,组件状态变更,会导致流中加入新值。

import { useState } from 'react';
import { useObservableState, useObservable } from 'observable-hooks';

function App() {
  const [state, setState] = useState(0);

  const transform = output$ => output$.pipe(
    //
  );
  const output$ = useObservable(  // 这里是关键
    transform,
    [state]
  );

  const initialOutput = state;
  const output = useObservableState(output$, initialOutput);

  const onClick = () => {
    setState(state + 1);
  };

  return (
    <>
      <input type="button" value="Add One" onClick={onClick} />
      {output}
    </>
  );
}
export default App;
  • useObservable 的第二个参数,传入了 output$ 依赖的变量列表 [state]
    state 值改变时,就会向流中加入这个值,随后 transform 对流进行了变换
  • 组件通过 setState 更新后 output$ 并没有变(是同一个对象),改变的只是流中的值

(5)事件写入流

功能:
页面展示一个按钮和数字 0,每次点击按钮,后面的数字加一。
按钮点击事件,会直接在流中加入新值。

import { useObservableState, useObservableCallback } from 'observable-hooks';

function App() {
  const transform = output$ => output$.pipe(
    //
  );
  const [onInput, output$] = useObservableCallback(  // 这里是关键
    transform
  )

  const initialOutput = 0;
  const output = useObservableState(output$, initialOutput);

  const onClick = () => {
    onInput(output + 1);
  };

  return (
    <>
      <input type="button" value="Add One" onClick={onClick} />
      {output}
    </>
  );
}
export default App;
  • useObservableCallback 会返回一个函数 onInput 和一个流 output$,调用这个函数 onInput 会向 output$ 中加入新值
(6)创建常量流

上文提到了 from 将 Promise 转换成流的做法,每次调用 from 都会产生新的流。
尤其是当组件通过 setState 状态更新的时候。

而使用下述写法,可以创建一个在组件更新时不变的流 output$
每次 useObservable 都返回相同的对象。

import { useState } from 'react';
import { from } from 'rxjs';
import { useObservable } from 'observable-hooks';

let a;
function App() {
  const [state, setState] = useState(0);

  const output$ = useObservable(
    () => from(new Promise(res => setTimeout(() => res(1), 500))),
  );

  const l = a === output$;
  debugger;     // 组件更新时 l === true
  a = output$;  // 保存上一次的值

  const onClick = () => {
    setState(state + 1);
  };

  return (
    <input type="button" value="Add One" onClick={onClick} />
  );
}
export default App;

3. 小结

  • 上文总共介绍了 4 个 hooks,用于了 6 个场景
// 异步取值 [流 -> 值]
const output = useObservableState(output$, initialOutput);

// 事件更新 [流变换 -> 值, 写入流]
const [output, onInput] = useObservableState(transform, initialOutput);

// 异步回调 [流, 回调 -> void]
useSubscription(output$, v => {
  console.log(v);
});

// 变量写入流 [流变换, 变量依赖 -> 常量流]
const output$ = useObservable(
  transform,
  [state],
);

// 事件写入流 [流变换 -> 写入流, 常量流]
const [onInput, output$] = useObservableCallback(
  transform,
);

// 创建同一个流 [流变换 -> 常量流]
const output$ = useObservable(
  () => from(new Promise(res => setTimeout(() => res(1), 500))),
);
  • 以上 hooks 都是建立在 “常量流” 基础之上的,即组件更新后 $output 并没有变,只是流中的值发生了变化。
import { useState } from 'react';
import { from } from 'rxjs';
import { useObservableState } from 'observable-hooks';

function App() {
  const [state, setState] = useState(0);

  const output$ = from(new Promise(res => setTimeout(() => res(1), 500)));
  const output = useObservableState(output$, 0);  // output$ 在组件更新后重置,所以 output 总是为 0

  const onClick = () => {
    setState(state + 1);
  };

  return (
    <>
      {output}
      <input type="button" value="Add One" onClick={onClick} />
    </>
  );
}
export default App;
  • 使用 Observable Hooks 时,我是这么考虑的:
    • 我们需要一个 “常量流”,在组件更新时不变(里面的值会变)
    • 依赖的变量改变了、或事件主动触发,都可以在流中添加值
    • 流中有了新值,会重新触发流变换函数 transform
    • 使用 useObservableState 从流中取值来用

参考

Observable Hooks
RxJS

相关文章

  • [FE] Hello "Observable Hooks"

    最近项目中用到了 react + rxjs + observable-hooks,下文总结一下 observabl...

  • observable-hooks

    用来连接普通值和 Observable提供 hook 来连接 observable 和 React 状态 为什么需...

  • [FE] hello assemblyscript

    1. 背景 AssemblyScript 是一个从 TypeScript 到 WebAssembly 的编译器。 ...

  • [FE] Hello Angular

    AngularJS 1.x是Angular的上一代框架,Angular团队做了规范,老框架为AngularJS 1...

  • [FE] Hello Vue

    1. 安装Vue (1)全局安装vue-cli (2)在工程目录中,创建一个基于 webpack 的新项目 (3)...

  • [FE] resolver.hooks.noResolve

    1. 背景 在使用webpack的时候,Can't resolve ... in ...这样的错误真是屡见不鲜了。...

  • [FE] hello-mobx

    1. 项目初始化 2. 安装开发环境依赖 3. 安装模块依赖 4. 新建./.babelrc和./webpack....

  • [FE] Hello Service Worker

    启动服务器 MDN: Service Worker文档中指出,Service Worker只能用在使用https协...

  • [FE] hello webpack loader

    webpack loader是一个node模块,它导出了一个函数,当使用该loader载入文件的时候,webpac...

  • [FE] Hello Ant Design

    1. 环境搭建: (1)安装node.js (2)更换npm为淘宝的源 (3)安装webpack,babel和脚手...

网友评论

    本文标题:[FE] Hello "Observable Hooks"

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