组件复用指南
React 组件复用指南
# 高阶组件
# 什么是高阶组件?
高阶组件就是一个 React 组件包裹着另外一个 React 组件
这种模式通常使用函数来实现,基本上是一个类工厂
hocFactory:: w: React.Component => E: React.Component
其中 W (WrappedComponent) 指被包裹的 React.Component,E (EnhancedComponent) 指返回类型为 React.Component 的新的 HOC。
- 属性代理(Props Proxy): HOC 对传给 WrappedComponent 的 props 进行操作
- 反向继承(Inheritance Inversion): HOC 继承 WrappedComponent
# HOC 工厂实现方法
# 属性代理 PP
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
};
}
2
3
4
5
6
7
# PP 可以做什么
- 操作 props
- 通过 Refs 访问到组件实例
- 提取 state
- 用其他元素包裹 WrappedComponent
# 通过 refs 访问到组件实例
你可以通过 ref 访问到 this,但为了得到引用,WrappedComponent 还需要一个初始渲染,意味着你需要在 HOC 的 render 方法中返回 WrappedComponent 元素,让 React 开始它的一致化处理,你就可以得到 WrappedComponent的实例的引用。
eg:
function refsHOC(WrappedComponent) {
return class RefsHOC extends React.Component {
proc(wrappedComponentInstance) {
wrappedComponentInstance.method();
}
render() {
const props = Object.assign({}, this.props, {
ref: this.proc.bind(this)
});
return <WrappedComponent {...props} />;
}
};
}
2
3
4
5
6
7
8
9
10
11
12
13
详细实现:
- a 方法:类似 React Redux 的 connect,添加一个参数 withRef,并通过 getWrappedInstance 方法获取
function refsHOC(wrappedComponent) {
return class HOC extends React.Component {
wrappedInstance = null;
getWrappedInstance = () => {
if (this.props.withRef) {
return this.wrappedInstance;
}
};
setWrappedInstance = ref => {
this.wrappedInstance = ref;
};
render() {
let props = { ...this.props };
if (this.props.withRef) {
props.ref = this.setWrappedInstance;
}
return <wrappedComponent {...props} />;
}
};
}
class ParentCompoent extends React.Component {
doSomethingWithMyComponent() {
let instance = this.refs.child.getWrappedInstance();
// ....
}
render() {
return <MyComponent ref="child" withRef />;
}
}
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
30
31
- b 方法:父级传一个方法来获取 ref:
class parent extends React.Component {
wrappedInstance = null;
getInstance = ref => {
this.wrappedInstance = ref;
};
render() {
return <MyComponent getInstance={this.getInstance} />;
}
}
function HOCFactory(wrappedComponent) {
return class HOC extends React.Component {
render() {
let props = {
...this.props
};
if (typeof this.props.getInstance === "function") {
props.ref = this.props.getInstance;
}
return <wrappedComponent {...props} />;
}
};
}
export default HOCFactory(MyComponent);
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
# hoist-non-react-statics
通过 hoist-non-react-statics 组件,将自动绑定所有在对象上的非 React 方法到新对象上,解决 static 方法找不到的问题
# 提取 state
你可以通过传入 props 和回调函数把 state 提取出来,eg:
function ppHOC(WrappedComponent) {
return class PP extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ""
};
this.onNameChange = this.onNameChange.bind(this);
}
onNameChange(event) {
this.setState({
name: event.target.value
});
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange
}
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
@ppHOC
class Example extends React.Component {
render() {
return <input name="name" {...this.props.name} />;
}
}
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
30
31
32
# 反向继承(Inheritance Inversion)
function iiHOC(WrappedComponent) {
return class Enhancer extends WrappedComponent {
render() {
return super.render();
}
};
}
2
3
4
5
6
7
ii 允许 HOC 通过 this 访问到 WrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。
# 一致化处理
React 元素有两种类型:字符串和函数。字符串类型的 React 元素代表 DOM 节点,函数类型的 React 元素代表继承 React.Component 的组件。更多关于元素(Element)和组件(Component)请看这篇文章 (opens new window)。函数类型的 React 元素会在一致化 (opens new window)处理中被解析成一个完全由字符串类型 React 组件组成的树(而最后的结果永远是 DOM 元素)。
# ii 的高阶组件不一定会解析完整子树
渲染劫持
- 在由 render 输出的任何 React 元素中读取、添加、删除 props
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { if (this.props.loggedIn) { return super.render(); } else { return null; } } }; }
1
2
3
4
5
6
7
8
9
10
11- 读取和修改由 render 输出的 React 元素树
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { const elementsTree = super.render(); let newProps = {}; if (elementsTree && elementsTree.type === "input") { newProps = { value: "may the force be with you" }; } const props = Object.assign({}, elementsTree.props, newProps); const newElementsTree = React.cloneElement( elementsTree, props, elementsTree.props.children ); return newElementsTree; } }; } //你可以遍历整个元素树,然后修改元素树中任何元素的 props
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19- 有条件地渲染元素树
- 把样式包裹进元素树(就像在 Props Proxy 中的那样)
# 操作 state
export function IIHOCDEBUGGER(WrappedComponent) { return class II extends WrappedComponent { render() { return ( <div> <h2>HOC Debugger Component</h2> <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre> <p>State</p> <pre>{JSON.stringify(this.state, null, 2)}</pre> {super.render()} </div> ); } }; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# HOC 和参数
function HOCFactoryFactory(...params) { return function HOCFactory(WrappedComponent) { return classHOC extends React.Component { render() { return <WrappedComponent {...this.props}/> } } } } //用法 HOCFactoryFactory(params)(WrappedComponent) //或 @HOCFatoryFactory(params) class WrappedComponent extends React.Component{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 渲染属性(Render Props)
# Mixins 存在的问题
从 createClass 的 mixins 技术说起。我们创建一个 MouseMixin.
import React from "react"; import ReactDOM from "react-dom"; // mixin 中含有了你需要在任何应用中追踪鼠标位置的样板代码。 // 我们可以将样板代码放入到一个 mixin 中,这样其他组件就能共享这些代码 const MouseMixin = { getInitialState() { return { x: 0, y: 0 }; }, handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } }; const App = React.createClass({ // 使用 mixin! mixins: [MouseMixin], render() { const { x, y } = this.state; return ( <div style={{ height: "100%" }} onMouseMove={this.handleMouseMove}> <h1> The mouse position is ({x}, {y}) </h1> </div> ); } }); ReactDOM.render(<App />, document.getElementById("app"));
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
30
31# HOC是新的Mixin
es6 class不支持mixins,且存在着命名冲突的问题
# Render Props
一个普通组件只需要一个函数prop就能够进行一些state共享
import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' //render props 组件共享代码 class Mouse extends React.Component { static propTypes = { render: PropTypes.func.isRequired } state = {x: 0, y: 0} handleMouseMove = e => { this.setState({ x: e.clientX, y: e.clientY }) } render() { return ( <div onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ) } } const App = React.createClass({ render() { return ( <div> <Mouse render={({x, y}) => ( <h3>The mouse position is ({x}, {y})</h3> )}/> </div> ) } })
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
30
31
32
33
34
35
36# Render Props > HOCs
一个更将强有力的,能够证明 render prop 比 HOC 要强大的证据是,任何 HOC 都能使用 render prop 替代,反之则不然。下面的代码展示了使用一个一般的、具有 render prop 的
组件来实现的 withMouse HOC: const withMouse = (Component) => { return class extends React.Component { render() { return <Mouse render={mouse => ( <Component {...this.props} mouse={mouse}/> )}/> } } }
1
2
3
4
5
6
7
8
9