共计 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 接口来填即可。