反 React 范式思考

🤔 反 React 范式思考

这是一个 loading 引发的故事

如何设计一个符合范式的 Loading

const App = () => {
  return <>
     {/** DO SOME THING **/}
     <Loading />
    </>
}

const Loading = () => {
  const { loadingVisible } = useStore()
  return loadingVisible ? '转圈圈' : null
}

我们在 store 中去定义 loading 的状态, 然后通过 action, transform, mutation, change, 之类的操作去改变 store, 使 react 驱动 loading 显示

这么做很符合数据驱动视图, 但是对应存在下面这些问题

  1. 不符合直觉, 直觉是我们打开和关闭 loading 而不是改变 store 中的某个状态

  2. loading 在最初加载不管 store 还是 view 都会存在前置加载的其余资源

  3. store 层, 如果放在顶层 store , store的状态机会变复杂, 如果分散在子 store 同样面临 store 管理的问题, 虽然这个问题状态管理的通病

反范式的 Loading

// file - loading.jsx
const Loading = () => <>转圈圈</>

export let loadingVisible = false

const loadingElm = document.createElement("div")

export function showLoading(){
  document.body.appendChild(loadingElm);
  ReactDOM.render(<Loading />, loadingElm);
  loadingVisible = true
}

export function hideLoading(){
  ReactDOM.unmountComponentAtNode(loadingElm);
  document.body.removeChild(loadingElm);
  loadingVisible = false
}

这种方法相当与我们直接操作 dom 去生成, 当然同样可以用这种方法来实现 toast alert 一些全局的通用组件

好处是变成了更符合直觉的命令式编程

命令式的 React

将这种写法进一步延伸, 我们可以做些什么

Demo 地址 https://codesandbox.io/s/beautiful-bhabha-8eb0h?file=/index.js

function confirmModal({ title = "", context }) {
  return new Promise((resolve, reject) => {
    const div = document.createElement("div");
    document.body.appendChild(div);
    const close = () =>
      ReactDOM.unmountComponentAtNode(div) && document.body.removeChild(div);
    const onOk = () => close() && resolve();
    const onCancel = () => close() && reject();

    ReactDOM.render(
      <Modal title={title} visible={true} onOk={onOk} onCancel={onCancel}>
        {context}
      </Modal>,
      div
    );
  });
}

async function confirm() {
  // 检测一些
  try {
    await confirmModal({ title: "是否确认", context: "是否确认信息" });
    console.log("确认");
  } catch (error) {
    console.log("取消");
  }
}

ReactDOM.render(
  <div className="App">
    <Button onClick={confirm}>确认</Button>
  </div>,
  document.getElementById("root")
);

通过 Promise 我们可以把一些原本需要写在局部组建内的一些状态控制变成, 更加符合直觉的命令式逻辑, 并把业务逻辑的主线是变得更加清晰

async function createItem() {
  // 检测一些
  const div = document.createElement("div");
  document.body.appendChild(div);
  const close = () =>
    ReactDOM.unmountComponentAtNode(div) && document.body.removeChild(div);
  const onFinish = async () => {
    // await 更新接口
    close();
  };
  const onFinishFailed = () => {
    // 错误提示
  };

  ReactDOM.render(
    <Modal title={"新建"} visible={true} footer={null}>
      <Form
        name="basic"
        labelCol={{ span: 8 }}
        wrapperCol={{ span: 16 }}
        initialValues={{ remember: true }}
        onFinish={onFinish}
        onFinishFailed={onFinishFailed}
        autoComplete="off"
      >
        <Form.Item
          label="Username"
          name="username"
          rules={[{ required: true, message: "Please input your username!" }]}
        >
          <Input />
        </Form.Item>

        <Form.Item
          label="Password"
          name="password"
          rules={[{ required: true, message: "Please input your password!" }]}
        >
          <Input.Password />
        </Form.Item>

        <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
          <Button type="primary" htmlType="submit">
            提交
          </Button>
        </Form.Item>
      </Form>
    </Modal>,
    div
  );
}

通过主动控制 React 的创建和销毁实现脱离当前文档流的逻辑

跨树的状态管理

因为我们通过 ReactDOM.render 创建了新的 react 节点树, 所以原有基于 ReactContext 的跨节点通信会失效, 所以需要跨 react 树通信的方式

可以直接通过类似 react-redux 等外部状态管理

demo: https://codesandbox.io/s/vibrant-shamir-qg97s?file=/index.js

import { Provider } from "react-redux";

<Provider store={reduxStore}>
 {/* 主线业务 */}
  <App />
</Provider>


<Provider store={reduxStore}>
   {/* 我们当前要做事情的组件 */}
  <DoSomeThing />
</Provider>

当然直接嵌套用主线业务的 store 可以完成通信, 但是很多时候未免显得太重, 毕竟包裹了一层

然后我找了一些 React 状态管理的库做参考 参考列表

设计了下面这个跨树跨组件通信的方法, 通过创建一个隐藏的 React tree 来包裹 一个或一段 hooks 的集合, 并共享这段 hooks 到消费的组件

demo https://codesandbox.io/s/goofy-kilby-gyi2o?file=/src/index.js



/**
 * 创建跨组件跨树通讯 Hooks (可以用于的跨组件使用)
 * 思路
 * 1. 通过创建一个隐藏 React Tree 来包裹 Hook
 * 2. Hook 变化触发隐藏 React Tree 渲染
 * 3. 返回的是一个被劫持的 Hook 当 隐藏 React Tree 渲染 时会更新劫持的 Hook 数据
 * @param {Function} hook 自定义的 useHooks
 */

export const createHookObserver = (hook) => {
  const div = document.createElement("div");
  const events = new Set();
  let $data;
  const update = (data) => {
    $data = data;
    events.forEach((event) => event(data));
    return null;
  };

  render(
    createElement(() => update(hook())),
    div
  );
  const useHooks = () => {
    const [val, setVal] = useState($data);
    useEffect(() => {
      events.add(setVal);
      return () => events.delete(setVal);
    }, []);
    return val;
  };

  return useHooks;
};


const useCount = createHookObserver(() => {
  const [count, setCount] = useState(0);
  return { count, setCount };
});

聊聊 React 设计

在 React 中所谓组件, 其实就是状态机器, 通过定义 state 推导出在当前状态下的视图状态.

设计 React 组件其实是在定义状态, 描述逻辑和现实的抽象.

我们能从 React 组件设计中体会到那种 less is more 的简洁美, 但绕回来说, 软件工程没有银弹.

当我们遇到的逻辑和现实问题不能够或很麻烦通过 React 那种简洁富有张力的描述方式去解决时, 我们应该怎么取舍

原教旨主义, 范式, 在什么样的场景下应该打破这些束缚

End

跳出框架来思考一些问题吧, 我们使用这个工具, 概念来解决我们的具体问题, 不应该这些东西限制住.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值