关于javascript:在普通对象的方法中使用React-Hooks写-Hook-的和写类组件的都沉默了

40次阅读

共计 3469 个字符,预计需要花费 9 分钟才能阅读完成。

在界面编写方面,我十分喜爱 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