乐趣区

从零开始学习 React 高阶组件

01、介绍

React 高阶组件也叫做 React HOC(High Order Component), 它是 react 中的高级技术, 用来重用组件逻辑。
但高阶组件本身并不是 React API。它只是一种模式,这种模式是由 react 自身的组合性质必然产生的。
那么在学习高阶组件之前有一个概念我们必须清楚,就是高阶函数。

02、高阶函数

概念:高阶函数是一个函数,它接收函数作为参数或将函数作为输出返回

举个栗子:

接收函数作为参数
function a(x) {
x();
}
function b() {
alert(‘hello’);
}

a(b);

将函数作为输出返回
function a() {
function b() {
alert(‘hello’);
}
return b;
}

a()();

以上函数 a 就是一个高阶函数, 用法非常简单, 那么实际开发中又有哪些是高阶函数呢?

Array 的 map、reduce、filter 等方法
Object 的 keys、values 等方法

03、高阶组件

概念:高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件

举个栗子:
// WrappedComponent 就是传入的包装组件
function withHoc(WrappedComponent) {
return class extends Component {
render () {
return (
<WrappedComponent />
)
}
}
}

withHoc 函数就是一个高阶组件。那么高阶组件到底有什么神奇的魔力,值得我们为之着迷?
开发组件时,我们会遇到相同的功能,使用高阶组件则能减少重复代码

04、高阶组件实训 1

目的: 定义高阶组件

组件 Login — 登陆页面
// 受控组件
class Login extends Component {
state = {
username: ”,
password: ”
}

onUsernameChange = (e) => {
this.setState({username: e.target.value});
}

onPasswordChange = (e) => {
this.setState({password: e.target.value});
}

login = (e) => {
// 禁止默认事件
e.preventDefault();
// 收集表单数据
const {username, password} = this.state;
alert(` 用户名: ${username}, 密码: ${password}`);
}

render () {
const {username, password} = this.state;
return (
<div>
<h2> 登陆 </h2>
<form onSubmit={this.login}>
用户名: <input type=”text” name=”username” value={username} onChange={this.onUsernameChange}/> <br/>
密码: <input type=”password” name=”password” value={password} onChange={this.onPasswordChange}/> <br/>
<input type=”submit” value=” 登陆 ”/>
</form>
</div>
)
}
}

组件 Register — 注册页面
// 受控组件
class Register extends Component {
state = {
username: ”,
password: ”,
rePassword: ”
}

onUsernameChange = (e) => {
this.setState({username: e.target.value});
}

onPasswordChange = (e) => {
this.setState({password: e.target.value});
}

onRePasswordChange = (e) => {
this.setState({rePassword: e.target.value});
}

register = (e) => {
// 禁止默认事件
e.preventDefault();
// 收集表单数据
const {username, password, rePassword} = this.state;
alert(` 用户名: ${username}, 密码: ${password}, 确认密码: ${rePassword}`);
}

render () {
const {username, password, rePassword} = this.state;
return (
<div>
<h2> 注册 </h2>
<form onSubmit={this.register}>
用户名: <input type=”text” name=”username” value={username} onChange={this.onUsernameChange}/> <br/>
密码: <input type=”password” name=”password” value={password} onChange={this.onPasswordChange}/> <br/>
确认密码: <input type=”password” name=”rePassword” value={rePassword} onChange={this.onRePasswordChange}/> <br/>
<input type=”submit” value=” 注册 ”/>
</form>
</div>
)
}
}

页面效果

我们发现里面重复逻辑实在太多了,尤其是 onXxxChange 函数出现太多,我们先优化一下。
// 我们以 Register 组件为例来看
class Register extends Component {
state = {
username: ”,
password: ”,
rePassword: ”
}
// 最终修改状态数据的函数
onChange = (stateName, stateValue) => {
this.setState({[stateName]: stateValue});
}
// 高阶函数 –> 这样后面就能一直复用当前函数,而不用重新创建了~
composeChange = (name) => {
return (e) => this.onChange(name, e.target.value);
}
// 统一所有提交表单函数名
handleSubmit = (e) => {
e.preventDefault();
const {username, password, rePassword} = this.state;
alert(` 用户名: ${username}, 密码: ${password}, 确认密码: ${rePassword}`);
}

render () {
const {username, password, rePassword} = this.state;
return (
<div>
<h2> 注册 </h2>
<form onSubmit={this.handleSubmit}>
用户名: <input type=”text” name=”username” value={username} onChange={this.composeChange(‘username’)}/> <br/>
密码: <input type=”password” name=”password” value={password} onChange={this.composeChange(‘password’)}/> <br/>
确认密码: <input type=”password” name=”rePassword” value={rePassword} onChange={this.composeChange(‘rePassword’)}/> <br/>
<input type=”submit” value=” 注册 ”/>
</form>
</div>
)
}
}

现在两个页面都有 onChange、composeChange、handleSubmit 函数和相关的状态,我们接下来提取,封装成高阶组件!
// 高阶组件 withHoc
export default function withHoc(WrappedComponent) {
return class extends Component {
state = {
username: ”,
password: ”,
rePassword: ”
}

onChange = (stateName, stateValue) => {
this.setState({[stateName]: stateValue});
}

composeChange = (name) => {
return (e) => this.onChange(name, e.target.value);
}

handleSubmit = (e) => {
e.preventDefault();
const {username, password, rePassword} = this.state;
if (rePassword) {
alert(` 用户名: ${username}, 密码: ${password}, 确认密码: ${rePassword}`);
} else {
alert(` 用户名: ${username}, 密码: ${password}`);
}
}

render () {
// 抽取方法
const mapMethodToProps = {
composeChange: this.composeChange,
handleSubmit: this.handleSubmit,
}
// 将状态数据和操作的方法以 props 的方式传入的包装组件中
return (
<div>
{/* 提取公共头部 */}
<h2>xxx</h2>
<WrappedComponent {…this.state} {…mapMethodToProps}/>
</div>
)
}
}
}

// 组件 Register
class Register extends Component {
render () {
const {handleSubmit, composeChange, username, password, rePassword} = this.props;
return (
<form onSubmit={handleSubmit}>
用户名: <input type=”text” name=”username” value={username} onChange={composeChange(‘username’)}/> <br/>
密码: <input type=”password” name=”password” value={password} onChange={composeChange(‘password’)}/> <br/>
确认密码: <input type=”password” name=”rePassword” value={rePassword} onChange={composeChange(‘rePassword’)}/> <br/>
<input type=”submit” value=” 注册 ”/>
</form>
)
}
}
// 向外暴露的是高阶组件的返回值~ 包装了 Register 组件返回了一个新组件
export default withHoc(Register);

现在我们提取了公共方法、状态等数据, 封装了一个基本的高阶组件。但是还有很多需要问题需要解决,现在开始行动~

05、高阶组件实训 2

目的: 向高阶组件中传参

修改高阶组件
// 再次包裹了一层高阶函数, 这个高阶函数执行后返回值才是高阶组件
// 通过这种方式, 高阶组件内部就能获取参数了~
export default (title) => (WrappedComponent) => {
return class Form extends Component {
… 重复代码省略 …

render () {
const mapMethodToProps = {
composeChange: this.composeChange,
handleSubmit: this.handleSubmit,
}
return (
<div>
{/* 获取到参数值就能正常显示了~*/}
<h2>{title}</h2>
<WrappedComponent {…this.state} {…mapMethodToProps}/>
</div>
)
}
}
}

在 Login / Register 组件中使用

export default withHoc(‘ 登陆 ’)(Login);
export default withHoc(‘ 注册 ’)(Register);

06、高阶组件实训 3

目的: 获取父组件传递的 props

修改 App 组件
class App extends Component {
render() {
return (
<div>
{/* 父组件向子组件传递属性 */}
<Login name=”jack” age={18}/>
<Register />
</div>
);
}
}

修改高阶组件
export default (title) => (WrappedComponent) => {
return class Form extends Component {
… 重复代码省略 …

render () {
const mapMethodToProps = {
composeChange: this.composeChange,
handleSubmit: this.handleSubmit,
}
return (
<div>
{/* 获取到参数值就能正常显示了~*/}
<h2>{title}</h2>
{/* 将当前组件接受到的 props 传给包装组件~*/}
<WrappedComponent {…this.props} {…this.state} {…mapMethodToProps}/>
</div>
)
}
}
}

Login 组件中使用
class Login extends Component {
render () {
const {handleSubmit, composeChange, username, password, name, age} = this.props;
return (
<div>
<p> 你的名字: {name}</p>
<p> 你的年龄: {age}</p>
<form onSubmit={handleSubmit}>
用户名: <input type=”text” name=”username” value={username} onChange={composeChange(‘username’)}/> <br/>
密码: <input type=”password” name=”password” value={password} onChange={composeChange(‘password’)}/> <br/>
<input type=”submit” value=” 登陆 ”/>
</form>
</div>
)
}
}

07、高阶组件实训 4

目的: 修改在 React-devtool 中高阶组件名称,方便调试

修改高阶组件
export default (title) => (WrappedComponent) => {
return class Form extends Component {
// 定义静态方法,修改组件在调试工具中显示的名称
static displayName = `Form(${getDisplayName(WrappedComponent)})`

… 省略重复代码 …
}
}
// 获取包装组件的 displayName 的方法
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || ‘Component’;
}

修改之前名称

修改之后名称

08、使用装饰器

目的: 简化使用高阶组件

下载包
npm i react-app-rewired customize-cra @babel/plugin-proposal-decorators -D

在项目根目录配置 config-overrides.js
const {override, addBabelPlugins} = require(‘customize-cra’);
// 修改 create-react-app 的 webpack 的配置
module.exports = override(
// 添加 babel 插件
addBabelPlugins(
[
“@babel/plugin-proposal-decorators”,
{
“legacy”: true
}
]
)
)

修改 package.json 的 scripts
// 将 react-scripts 修改为 react-app-rewired
“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”
},

以上就是使用 decorator 的配置,修改完后就能使用了~

修改 Login 组件
@withHoc(‘ 登陆 ’)
class Login extends Component {
… 省略重复代码 …
}
export default Login;

修改 Register 组件
@withHoc(‘ 注册 ’)
class Register extends Component {
… 省略重复代码 …
}
export default Register;

react-app-rewired customize-cra 是 create-react-app 2.0 以上专门用来修改 webpack 的配置
decorator 还能做很多事,感兴趣朋友可以看看 阮一峰 ES6 教程 了解更多

重复代码永远是我们需要考虑处理的代码,所以我们有模块化、组件化、工具类函数等等,在 React 中再次引入了一个高阶组件的概念,都是为了去除掉万恶的重复代码,让我们代码变得更加精简。本篇文章所有源码都放在了 git 仓库,如果它对你有帮助的话,欢迎点 star ~~

退出移动版