在界面编写方面,我十分喜爱 React Hooks + 函数组件的写法。
然而在写简单利用的时候,我更喜爱应用面向对象的写法,所以当我用面向对象写法写了一大堆,因为我心愿利用与视图剥离,只在实现的时候才绑定视图,甚至可能把渲染办法换成 Vue 等其余计划,所以没有思考基于 React.Component 的计划来开发。
然而等我终于打算渲染进去看成果的时候却犯了难:我不能在类的办法中应用 hooks ,而应用类组件又十分繁琐。

1. 首次尝试:应用 React Hooks

1.1 间接应用是行不通的

最好能够间接在类的办法外面间接应用 Hook ,当然我本人会做一些 Hook 的绑定工作:

// 留神这里的 ViewComponent 并非继承至 React.Componentclass 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 接口来填即可。