在普通对象的方法中使用“React Hooks”,写 Hook 的和写类组件的都沉默了

在界面编写方面,我非常喜欢 React Hooks + 函数组件的写法。
但是在写复杂应用的时候,我更喜欢使用面向对象的写法,所以当我用面向对象写法写了一大堆,由于我希望应用与视图剥离,只在实现的时候才绑定视图,甚至可能把渲染方法换成 Vue 等其他方案,所以没有考虑基于 React.Component 的方案来开发。
但是等我终于打算渲染出来看效果的时候却犯了难:我不能在类的方法中使用 hooks ,而使用类组件又非常繁琐。

1. 首次尝试:使用 React Hooks

1.1 直接使用是行不通的

最好可以直接在类的方法里面直接使用 Hook ,当然我自己会做一些 Hook 的绑定工作:

// 注意这里的 ViewComponent 并非继承至 React.Component
class Test extends Editor.ViewComponent {
  render() {
    const [{ count }, setCount] = useState(this.data);

    // 使用自定义的事件机制把 setState 和类的 .data 属性关联起来
    // 之所以要这样做是因为 Editor.ViewComponent 有自己的属性管理
    // 机制,包括大量不应直接影响渲染的东西
    useEffect(() => {
        this.onDataChange(setCount);
        return () => this.offDataChange(setCount);
    }, []);
    return (
      <button onClick={this.changeData({ count: this.data.count + 1 })}>
        点击次数: {count}
      </button>
    );
  }
}

👆然而任何一个熟悉 React Hooks 的人都知道,React Hooks 必须是在纯函数里面使用,而类组件的 this 自带状态,所以这段代码不能通过编译。

1.2 换个法子——使用回调订阅机制

具体的实现方法就是使用一个通信机制,将类实例中的变化传递到一个纯函数里面去,纯函数中搜集的事件也通过此方法回传:

// LayerButtonBus 专门给 LayerButtons 和对应的纯函数“跑腿”
export class LayerButtonBus extends ViewEventBus {
  static HIDDEN_LAYER = "HIDDEN_LAYER";
  static SHOW_LAYER = "HIDDEN_LAYER";
  static DELETE_LAYER = "DELETE_LAYER";
}

// LayerButtonRender 是专门给 LayerButtons 渲染视图的纯函数
function LayerButtonRender({ viewBus }: { viewBus: LayerButtonBus }) {
  // …… 细节代码极其繁琐,就不贴出来了
  return <div>{
     buttons.map((Button, params) => <Button {...params}/>) 
  }<div/>
}

// LayerButton 本尊
export class LayerButton extends Editor.ViewComponent{
    render(){
        return <LayerButtonRender viewBus={this.viewEventBus} />;
    }
}

👆 这样写是可以运行的,但是每一种有状态的视图类都需要这样“三件套”,心智负担和工作量都相当大,非人哉!

2. 放弃 React Hooks ,使用类组件

类组件是可以直接使用的,只要你不嫌麻烦:

class Test extends Editor.ViewComponent {
    render(){
        return class View extends React.Component{
            // 这里就省略掉实现细节了
            render(){
                return <button>点击次数:{this.state.count}</button>
            }
        }
    }
}

👆其实也没什么大问题,但是写类组件也算是繁琐事情一大桩,并且不能使用 Hooks 。

3. 另辟蹊径

类组件工厂

上面这种使用类组件的写法是可行的,那么只需要把类组件的实现细节封装起来,就可以了:

const {
    bindReactClass
} = Editor.ViewComponent;
class Test extends Editor.ViewComponent {
    render(){
        const _this = this;
        return bindReactClass({
            state: _this.data,
            // 在 constructor 里执行:
            onInit(){
                _this.onDataChange(this.setState);
            },
            // componentWillUnmount
            willUnmount(){
                _this.offDataChange(this.setState);
            },
            // 渲染视图
            jsx: <button>点击次数:{_this.data.count}</button>
        })
    }
}

👆虽然看起来像是在写 Vue ,但是比起手撸一个完整的类还是方便了不少。

基于闭包特性的 “React Hooks” 的

React 的函数组件虽然是“纯函数”,但我要说:因为 Hooks 的引入,函数组件其实已经不那么“纯”了,所以我们大可不必继续“装纯”,拥有状态的同时,也要拥抱自主可控的副作用。
那么函数里面什么东西最适合用来搞副作用呢?答案已经呼之欲出了——闭包特性——在那个蛮荒的年代我们曾使用这种特性搞定私有属性,而如今它可以为我们的函数写法维持状态。
我相信大部分习惯 React Hooks 的人可以很快适应这种写法:

const Test = ({ useState }: HooksType) => {
  // stateSample 只是一个样子货,不具备响应性,不能直接用来渲染
  // 算是一种新的“闭包陷阱”
  const [countSample, setCount] = useState(count);

  return ({ count }: typeof { count: countSample }) => {
    // 这里只用于渲染,不能调用 “Hooks”,不然行为会变得诡异
    return <button onClick = {()=>setCount(count + 1)} >点击次数:{count}</button>
  }
}

不过具体的实现上, state 使用的是类组件的玩法,也就是一个组件只有一个 state ,使用键值对来维护,主要是因为懒得改。具体的实现如下(注意和上面使用的不是同一套方案):

const hookLikeClousure = (fun: typeof func) => {
  return class extends Component {
    state = {};

    private renderer!: (arg: any) => ReactNode;
    private watchedRefs = new Array<[RefObject<any>, (ref: any) => any]>();
    private initState!:{};

    constructor(props = {}) {
      super(props);
      let activedState = false;
      this.renderer = fun({
        getState: (initState) => {
          if (activedState) {
            throw new Error(
              "一个组件只能声明一次状态,多个状态请置为同一个对象的不同属性"
            );
          }
          activedState = true;
          this.initState = initState;

          return (newState: {}) => {
            this.setState(newState);
          };
        },
        awaitRef: <R extends any>(callback: (ref: R) => any) => {
          const ref = createRef<R>();
          this.watchedRefs.push([ref, callback]);
          return ref;
        },
      });
    }

    componentDidMount(){
        this.setState(this.initState);
        this.watchedRefs.forEach(([ref, callback]) => {
            callback(ref.current);
        });
    }

    render(): ReactNode {
      return <>{this.renderer(this.state)}</>;
    }
  };
};

👆 把 ref 的行为改了一改,但是隐约觉得有什么坑在里头,所以命名也没有沿袭 useRef而是使用 awaitRef,之后遇到坑的话需要就定义其他的 ref 接口来填即可。

0 条评论
请不要发布违法违规有害信息,如发现请及时举报或反馈
还没有人评论呢,速度抢占沙发!
相关文章
  • render阶段的入口render阶段的主要工作是构建Fiber树和生成effectList,在第5章中我们知道了react入口的两种模式会进入performSyncWorkOnRoot或者perfo...

  • 我们常说的调度,可以分为两大模块,时间分片和优先级调度时间分片的异步渲染是优先级调度实现的前提优先级调度在异步渲染的基础上引入优先级机制控制任务的打断、替换。本节将从时间分片的实现剖析react的异步...

  • React 全家桶-React基础 用于构建用户界面的JavaScript库。 facebook开源、组件化、声明式编码、React Native移动端开发、虚拟DOM+Diffing算法 官网:ht...

  • 一、写一个时钟用 react 写一个每秒都可以更新一次的时钟import React from 'react' import ReactDOM from 'react-dom' function ...

  • 目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api。大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux)。想想项目...

  • props props简单使用 class Person extends React.Component { render() { ...

  • 一,介绍React-Redux 是沟通 React 和 Redux 的桥梁,它主要功能体现在如下两个方面:在根组件中接受 Redux 的 Store,并把它合理分配到所需要的组件中。组件实例订阅 St...

  • 其他章节请看: react 高效高质量搭建后台系统 系列 脚手架搭建 本篇主要创建新项目 myspug,以及准备好环境(例如:安装 spug 中用到的包、本地开发和部署、自定义配置 react-ap...

  • 先看一下FiberNode在源码中的样子FiberNode// packages/react-reconciler/src/ReactFiber.old.js function FiberNode( ...

  • 有足够的地图数据,可以点击到街道,示例我只出到市级 以umi为框架,版本是:   "react": "^18.2.0",   "umi": "^4.0.29",   "echarts": "^5...

  • hook调用入口 在hook源码中hook存在于Dispatcher中,Dispatcher就是一个对象,不同hook 调用的函数不一样,全局变量ReactCurrentDispatcher....

  • react启动的模式react有3种模式进入主体函数的入口,我们可以从 react官方文档,使用 Concurrent 模式(实验性) 中对比三种模式:legacy 模式: ReactDOM.rend...

  • 同作为MVVM框架,React相比于Vue来讲,上手更需要JavaScript功底深厚一些,本系列将阅读React相关源码,从jsx -> VDom -> RDOM等一些列的过程,将会在本系列中一一讲...

  • 这是我的剖析 React 源码的第二篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。文章相关资料React 16.8.6 源码中文注释,...

  • 热身准备明确几个概念在React@17.0.3版本中:所有事件都是委托在id = root的DOM元素中(网上很多说是在document中,17版本不是了);在应用中所有节点的事件监听其实都是在id ...

  • 如何实现在react现有项目中嵌入Blazor? 目前官方只提供了angular和react俩种示例所以本教程只将react教程 思路讲解: 首先在现有react项目中我们可能某些组件是在Blazor...

  • 分析的源码来自https://github.com/alibaba/ho...useUrlState简介useUrlState是一个通过url query来管理state的Hook。url query...

  • 作为一款具有京东风格的组件库,我们一直致力于用心打造更符合开发者体验的组件库。NutUI-React v1 上线后我们团队也在不断的优化、测试、使用、迭代相关组件,但是在跨端小程序的开发过程中,Rea...

  • setState&forceUpdate在react中触发状态更新的几种方式:ReactDOM.renderthis.setStatethis.forceUpdateuseStateuseReduce...

  • React 设计体系如人类社会一般,拨动时间轮盘的那一刻,你便成了穿梭在轮片中的一粒细沙,角逐过程处处都需要亮出你的属性,你重要吗?你无可替代吗?你有特殊权限吗?没有,那不好意思,请继续在轮片中循环。...