这不仅仅是性能问题,重新挂载组件会导致该组件及其所有子组件的状态丢失,如果在组件之外创建HOC,这样一来组件只会创建一次。因此每次render时都会是同一个组件,一般来说,这跟你的预期表现是一致的。在极少数情况下,你需要动态调用HOC,你可以在组件的生命周期方法或其构造函数中进行调用。
务必复制静态方法有时在React组件上定义静态方法很有用,例如Relay容器暴露了一个静态方法getFragment以方便组合GraphQL片段。但是当你将HOC应用于组件时,原始组件将使用容器组件进行包装,这意味着新组件没有原始组件的任何静态方法。
// 定义静态函数 WrappedComponent.staticMethod = function() {/*...*/} // 现在使用 HOC const EnhancedComponent = enhance(WrappedComponent); // 增强组件没有 staticMethod typeof EnhancedComponent.staticMethod === "undefined" // true为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上。
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必须准确知道应该拷贝哪些方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }但要这样做,你需要知道哪些方法应该被拷贝,你可以使用hoist-non-react-statics依赖自动拷贝所有非React静态方法。
import hoistNonReactStatic from "hoist-non-react-statics"; function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} hoistNonReactStatic(Enhance, WrappedComponent); return Enhance; }除了导出组件,另一个可行的方案是再额外导出这个静态方法。
// 使用这种方式代替... MyComponent.someFunction = someFunction; export default MyComponent; // ...单独导出该方法... export { someFunction }; // ...并在要使用的组件中,import 它们 import MyComponent, { someFunction } from "./MyComponent.js"; Refs不会被传递虽然高阶组件的约定是将所有props传递给被包装组件,但这对于refs并不适用,那是因为ref实际上并不是一个prop,就像key一样,它是由React专门处理的。如果将ref添加到HOC的返回组件中,则ref引用指向容器组件,而不是被包装组件,这个问题可以通过React.forwardRef这个API明确地将refs转发到内部的组件。。
function logProps(Component) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log('old props:', prevProps); console.log('new props:', this.props); } render() { const {forwardedRef, ...rest} = this.props; // 将自定义的 prop 属性 “forwardedRef” 定义为 ref return <Component ref={forwardedRef} {...rest} />; } } // 注意 React.forwardRef 回调的第二个参数 “ref”。 // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef” // 然后它就可以被挂载到被 LogProps 包裹的子组件上。 return React.forwardRef((props, ref) => { return <LogProps {...props} forwardedRef={ref} />; }); } Render Props与HOC一样,Render Props也是一直以来都存在的元老级模式,render props指在一种React组件之间使用一个值为函数的props共享代码的简单技术,具有render props的组件接收一个函数,该函数返回一个React元素并调用它而不是实现一个自己的渲染逻辑,render props是一个用于告知组件需要渲染什么内容的函数props,也是组件逻辑复用的一种实现方式,简单来说就是在被复用的组件中,通过一个名为render(属性名也可以不是render,只要值是一个函数即可)的prop属性,该属性是一个函数,这个函数接受一个对象并返回一个子组件,会将这个函数参数中的对象作为props传入给新生成的组件,而在使用调用者组件这里,只需要决定这个组件在哪里渲染以及该以何种逻辑渲染并传入相关对象即可。
对比HOC与Render Props,技术上,二者都基于组件组合机制,Render Props拥有与HOC 一样的扩展能力,称之为Render Props,并不是说只能用来复用渲染逻辑,而是表示在这种模式下,组件是通过render()组合起来的,类似于HOC 模式下通过Wrapper的render()建立组合关系形式上,二者非常相像,同样都会产生一层Wrapper,而实际上Render Props 与HOC 甚至能够相互转换。
同样,Render Props也会存在一些问题:
数据流向更直观了,子孙组件可以很明确地看到数据来源,但本质上Render Props是基于闭包实现的,大量地用于组件的复用将不可避免地引入了callback hell问题。
丢失了组件的上下文,因此没有this.props属性,不能像HOC那样访问this.props.children。
示例 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>React</title> </head> <body> <div></div> </body> <script src="http://unpkg.com/react@17/umd/react.development.js"></script> <script src="http://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script src="http://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> class MouseTracker extends React.Component { constructor(props) { super(props); this.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)} {/* Render Props */} </div> ) } } class MouseLocation extends React.Component { render() { return ( <> <h1>请在此处移动鼠标</h1> <p>当前鼠标的位置是: x:{this.props.mouse.x} y:{this.props.mouse.y}</p> </> ) } } ReactDOM.render( <MouseTracker render={mouse => <MouseLocation mouse={mouse} />}></MouseTracker>, document.getElementById("root") ); </script> </html> Hooks