提及逻辑复用,熟悉 react 小伙伴们一口道出了 HOC [高阶组件] 。没错,高阶组件能够实现逻辑复用,在 hook 以前 react 还有挺多不错的方案。那么,让咱们来浅谈 HOC 与 自定义 hook。react
提及HOC,我想到了两个标签:1.【嵌套】 2.【一直嵌套】数组
让咱们来深刻场景,举个例子:app
咱们常常会这样去作一个双向绑定dom
// ... state = { value: 1 }; onChange = (e: any) => { this.setState({ value: e.target.value }); }; // ... <input value={this.state.value} onChange={this.onChange} />;
假设在一个组件内有多个 input 咱们但愿能够更好的去复用「双向绑定」的逻辑,因而咱们对这块逻辑用 HOC 进行抽象:this
HOCInput.tsx双向绑定
const HOCInput = (WrappedComponent: any) => { return class extends React.Component< {}, { fields: { [key: string]: { value: string; onChange: (e: any) => void; }; }; } > { constructor(props: any) { super(props); this.state = { fields: {} }; } setField = (name: string) => { if (!this.state.fields[name]) { this.state.fields[name] = { value: "", onChange: (event: any) => { this.state.fields[name].value = event.target.value; this.forceUpdate(); } }; } return { value: this.state.fields[name].value, onChange: this.state.fields[name].onChange }; }; getFieldValueTrim = (name: string) => { return this.state.fields[name] ? this.state.fields[name].value.trim() : ""; }; render() { const { setField, getFieldValueTrim } = this; const newProps = { setField, getFieldValueTrim }; return <WrappedComponent {...this.props} {...newProps} />; } }; };
在 Vue 中有不错的 v-model.trim 语法糖【自动去掉字符串头尾空格】,避免咱们提交的时候有多余的空格。因此,这里咱们实现这样的功能并将 getFieldValueTrim
方法挂到 props 上。code
调用字符串
Demo1Component.tsxget
class Demo1Component extends React.Component<{ setField?: (name: string) => { value: string; onChange: (e: any) => {} }; getFieldValueTrim?: (name: string) => string; }> { render() { const { setField, getFieldValueTrim } = this.props; console.log("name :>> ", getFieldValueTrim!("name")); return ( <div> <input {...setField!("name")} /> <br /> <input {...setField!("email")} /> </div> ); } } // 嵌套 const Demo1 = HOCInput(Demo1Component); //... <Demo1 /> // ...
这样,咱们就用 HOC 完成了一个逻辑复用。假设,咱们还有一个或多个「逻辑」须要抽象成一个高阶组件呢?input
如:我想要点击按钮随机切换 input 框的背景颜色。
那就让咱们继续封装 HOC
HOCInputBgColor.tsx
const HOCInputBgColor = (initialColor: string) => (WrappedComponent: any) => { return class extends React.Component<{}, { color: string }> { state = { color: initialColor }; getRandomColor = () => { const randomNum = () => Math.floor(Math.random() * 100); return `rgb(${randomNum()},${randomNum()},${randomNum()})`; }; handleChangeColor = () => this.setState({ color: this.getRandomColor() }); render() { const newProps = { color: this.state.color, handleChangeColor: this.handleChangeColor }; return <WrappedComponent {...this.props} {...newProps} />; } }; };
在原来的组件上进行调用
Demo1Component.tsx
class Demo1Component extends React.Component<{ setField?: (name: string) => { value: string; onChange: (e: any) => {} }; getFieldValueTrim?: (name: string) => string; color?: string; handleChangeColor?: () => void; }> { render() { const { setField, getFieldValueTrim, color, handleChangeColor } = this.props; return ( <div> <input style={{ background: color! }} {...setField!("name")} /> <br /> <button onClick={handleChangeColor!}>change bg-color</button> </div> ); } } / const Demo1 = HOCInput(HOCInputBgColor("rgb(158,158,158)")(Demo1Component));
当咱们有更多的 HOC 时,那么就会一直嵌套下去,好在有ts装饰器的支持,让咱们看这个「嵌套」看着更加温馨,如:
@HOCInput @HOCInputBgColor("rgb(158,158,158)") class Demo1Component extends React.Component { }
咱们也再也不须要从新把组件赋值给一个变量,在调用组件的时候,直接 <Demo1Component />
。
当组件在调用多个HOC时,会调用 props 上 HOC 传递下来的 值/方法,如上面的例子:
const { setField, getFieldValueTrim, color, handleChangeColor } = this.props;
要是 HOC 一多命名就要造成规范,不然将有可能致使重命名发生覆盖。这算是 HOC 的一个缺点吧。
官网:自定义 hook 解决了之前在 React 组件没法灵活共享逻辑的问题。
咱们直接把上面的例子改为 hook 版看看。
useInput.ts【自定义 Hook 名称须要以 “use” 开头】
const useInput = ( initialValue = "" ): [{ value: string; onChange: (e: any) => void }, string] => { const [value, setValue] = useState(initialValue); const onChange = (e: any) => { setValue(e.target.value); }; return [ { value, onChange }, `${value}`.trim() ]; };
使用
Demo2.tsx
const Demo2: React.FC = () => { const [nameIpt, name] = useInput(); const [emailIpt, email] = useInput(); console.log("Hook-name :>> ", name); console.log("Hook-email :>> ", email); return ( <div> <input {...nameIpt} /> <br /> <input {...emailIpt} /> </div> ); };
能够明显的看到几个优势:
若是还有更多的逻辑须要被抽象,咱们只管继续封装 useXxx
,而后在组件中进行使用。
如上面讲的 HOCInputBgColor
高阶组件,咱们也能够用 hook版进行封装,如 useInputBgColor
,小伙伴们,动手试试看吧~
在数据的处理中,咱们知道在处理“平级”的数据,每每比嵌套的、树形的数据来得简单。
就如:
const arr = [1, [2, 3, [4, 5]]]; arr.flat('Infinity'); // [1, 2, 3, 4, 5]
我的以为 自定义hook 就相似这样一个“拉平”,让咱们对于数据的处理更直观,更不容易犯错。