1、HOC 高阶组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| export const withSize = (Component) => {
return class toSize extends React.Component { state = { xPos: document.documentElement.clientWidth, yPos: document.documentElement.clientHeight }; getPos = () => { this.setState({ xPos: document.documentElement.clientWidth, yPos: document.documentElement.clientHeight }) } componentDidMount() { window.addEventListener('resize', this.getPos); } componentWillUnmount() { window.removeEventListener('resize', this.getPos); }
render() { return <Component {...this.state}/> } } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| const SubWithFoo = withSize(Foo) const SubWithBar = withSize(Bar)
export default class HOCPage extends React.Component {
render() { return <> <SubWithFoo/> <SubWithBar/> </> } }
|
具体实践
渲染劫持:可以根据部分参数去决定是否渲染组件。
1 2 3 4 5 6 7 8 9 10
| const HOC = (WrappedComponent) => return class extends WrappedComponent { render() { if (this.props.isRender) { return super.render(); } else { return <div>Loading...</div>; } } }
|
权限控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function AuthWrapper(WrappedComponent) { return class AuthWrappedComponent extends React.Component { state = { permissionDenied: -1, }; render() { const { permissionDenied } = this.state; if (permissionDenied === -1) { return null; } if (permissionDenied) { return <div>功能即将上线,敬请期待~</div>; } return <WrappedComponent {...this.props} />; } } }
|
登录态的判断:
1 2 3 4 5 6 7 8 9
| const isLogin = !!localStorage.getItem('token'); const checkLogin = (WrappedComponent) => { return (props) => { return (isLogin ? <WrappedComponent {...props}/> : <LoginPage/>) } }
class RawUserPage extends Component {...} const UserPage = checkLogin(RawUserPage);
|
缺陷:
丢失静态函数
当我们应用HOC
去增强另一个组件时,我们实际使用的组件已经不是原组件了,所以我们拿不到原组件的任何静态属性,我们可以在HOC
的结尾手动拷贝他们:
1 2 3 4 5 6 7 8 9 10
| function proxyHOC(WrappedComponent) { class HOCComponent extends Component { render() { return <WrappedComponent {...this.props} />; } } HOCComponent.staticMethod = WrappedComponent.staticMethod; return HOCComponent; }
|
refs属性不能透传
使用高阶组件后,获取到的ref
实际上是最外层的容器组件,而非原组件,但是很多情况下我们需要用到原组件的ref
。
高阶组件并不能像透传props
那样将refs
透传,我们可以用一个回调函数来完成ref
的传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function hoc(WrappedComponent) { return class extends Component { getWrappedRef = () => this.wrappedRef; render() { return <WrappedComponent ref={ref => { this.wrappedRef = ref }} {...this.props} />; } } } @hoc class Input extends Component { render() { return <input></input> } } class App extends Component { render() { return ( <Input ref={ref => { this.inpitRef = ref.getWrappedRef() }} ></Input> ); } }
|
2、render props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class MouseTracker extends React.Component { state = { x: 0, y: 0, }
handleMouseMove = event => { this.setState({ x: event.clientX, y: event.clientY, }) }
render() { return ( <div onMouseMove={this.handleMouseMove} > {this.props.render(this.state)} </div> ) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| import MouseTracker from './MouseTracker'
function Tracker() { return ( <MouseTracker render={props => ( <div style={{ height: '100vh' }}> {props.x},{props.y} </div> )} /> ) }
|
3、自定义Hooks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| export const useTracking = () => { const [tracking, setTracking] = useState({});
useDeepEffect( () => { switch (tracking.eventType) { case 'pageview': gaPageView(tracking); gaFunnelStep(tracking.funnelStep, tracking.eventName); break; default: gaNewEvent(tracking); break; } }, [tracking], ); return [tracking, setTracking]; };
|
1 2 3 4
| const useError = () => { const { setError, cleanError, cleanToastBanner } = useContext(ErrorBoundaryContext); return { setError, cleanError, cleanToastBanner }; };
|
好处:
- 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
- 类定义更为复杂
- 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
- 时刻需要关注this的指向问题;
- 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
- 状态与 UI 隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。