React 深刻系列,深刻讲解了React中的重点概念、特性和模式等,旨在帮助你们加深对React的理解,以及在项目中更加灵活地使用React。html
本篇是React深刻系列的最后一篇,将介绍开发React应用时,常常用到的模式,这些模式并不是都有官方名称,因此有些模式的命名并不必定准确,请读者主要关注模式的内容。前端
React 组件的数据流是由state和props驱动的,但对于input、textarea、select等表单元素,由于能够直接接收用户在界面上的输入,因此破坏了React中的固有数据流。为了解决这个问题,React引入了受控组件,受控组件指input等表单元素显示的值,仍然是经过组件的state获取的,而不是直接显示用户在界面上的输入信息。react
受控组件的实现:经过监听表单元素值的变化,改变组件state,根据state显示组件最终要展现的值。一个简单的例子以下:编程
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
复制代码
和受控组件对应的概念是非受控组件,非受控组件经过ref获取表单元素的值,在一些场景下有着特有的做用(如设置表单元素的焦点)。redux
容器组件和展现组件是一组对应的概念,关注的是组件逻辑和组件展现的分离。逻辑由容器组件负责,展现组件聚焦在视图层的展示上。在React 深刻系列2:组件分类中已对容器组件和展现组件做过详细介绍,这里再也不赘述。后端
高阶组件是一种特殊的函数,接收组件做为输入,输出一个新的组件。高阶组件的主要做用是封装组件的通用逻辑,实现逻辑的复用。在React 深刻系列6:高阶组件中已经详细介绍太高阶组件,这里也再也不赘述。bash
首先,这个模式的命名可能并不恰当。这个模式中,借助React 组件的children属性,实现组件间的解耦。经常使用在一个组件负责UI的框架,框架内部的组件能够灵活替换的场景。app
一个示例:框架
// ModalDialog.js
export default function ModalDialog({ children }) {
return <div className="modal-dialog">{ children }</div>;
};
// App.js
render() {
<ModalDialog>
<SomeContentComp/>
</ModalDialog>
}
复制代码
ModalDialog组件是UI的框,框内组件能够灵活替换。ide
Render Props是把组件部分的渲染逻辑封装到一个函数中,利用组件的props接收这个函数,而后在组件内部调用这个函数,执行封装的渲染逻辑。
看一个官方的例子:
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
* Mouse组件并不知道应该如何渲染这部份内容,
* 这部分渲染逻辑是经过props的render属性传递给Mouse组件
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
复制代码
Mouse监听鼠标的移动,并将鼠标位置保存到state中。但Mouse组件并不知道最终要渲染出的内容,须要调用this.props.render方法,执行渲染逻辑。本例中,Cat组件会渲染到鼠标移动到的位置,但彻底可使用其余效果来跟随鼠标的移动,只需更改render方法便可。因而可知,Mouse组件只关注鼠标位置的移动,而跟随鼠标移动的界面效果,由使用Mouse的组件决定。这是一种基于切面编程的思想(了解后端开发的同窗应该比较熟悉)。
使用这种模式,通常习惯将封装渲染逻辑的函数赋值给一个命名为render的组件属性(如本例所示),但这并非必需,你也可使用其余的属性命名。
这种模式的变种形式是,直接使用React组件自带的children属性传递。上面的例子改写为:
class Mouse extends React.Component {
// 省略
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{/*
* Mouse组件并不知道应该如何渲染这部份内容,
* 这部分渲染逻辑是经过props的children属性传递给Mouse组件
*/}
{this.props.children(this.state)}
</div>
);
}
}
Mouse.propTypes = {
children: PropTypes.func.isRequired
};
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
</div>
);
}
}
复制代码
注意children的赋值方式。
React Router 和 React-Motion 这两个库都使用到了Render Props模式。**不少场景下,Render Props实现的功能也能够经过高阶组件实现。**本例也能够用高阶组件实现,请读者自行思考。
这种模式借助React的context,把组件须要使用的数据保存到context,并提供一个高阶组件从context中获取数据。
一个例子:
先建立MyProvider,将共享数据保存到它的context中,MyProvider通常做为最顶层的组件使用,从而确保其余组件都能获取到context中的数据:
import React from "react";
import PropTypes from "prop-types";
const contextTypes = {
sharedData: PropTypes.shape({
a: PropTypes.bool,
b: PropTypes.string,
c: PropTypes.object
})
};
export class MyProvider extends React.Component {
static childContextTypes = contextTypes;
getChildContext() {
// 假定context中的数据从props中获取
return { sharedData: this.props.sharedData };
}
render() {
return this.props.children;
}
}
复制代码
而后建立高阶组件connectData,用于从context中获取所需数据:
export const connectData = WrappedComponent =>
class extends React.Component {
static contextTypes = contextTypes;
render() {
const { props, context } = this;
return <WrappedComponent {...props} {...context.sharedData} />;
}
};
复制代码
最后在应用中使用:
const SomeComponentWithData = connectData(SomeComponent)
const sharedData = {
a: true,
b: "react",
c: {}
};
class App extends Component {
render() {
return (
<MyProvider sharedData={sharedData}>
<SomeComponentWithData />
</MyProvider>
);
}
}
复制代码
Provider组件模式很是实用,在react-redux、mobx-react等库中,都有使用到这种模式。
React 深刻系列文章到此完结,但愿能帮助你们更加深刻的理解React,更加纯熟的应用React。
个人新书《React进阶之路》已上市,对React感兴趣的同窗不妨去了解下。 购买地址: 当当 京东
欢迎关注个人公众号:老干部的大前端