React中的高阶组件(一)

296 阅读13分钟

高阶组件(Higher-Order Component, HOC) 是React中为了复用组件的函数。一个 HOC 是一个函数,接收一个组件并返回一个增强版的组件。通过 HOC,开发者可以将通用逻辑提取到可复用的组件包装器中,避免代码重复。

高阶组件这个名称来源于 高阶函数 的概念。高阶函数是指把函数作为参数传递,返回函数的函数。类似的,高阶组件 是一种 接受组件作为参数,返回增强版组件的函数。它不直接修改原始组件,而是生成一个包含增强功能的新组件。

1. withRouter(React Router)

  • 功能:使用 withRouter 高阶组件(HOC)来增强 React 组件,使其能够访问 react-router-dom 提供的路由相关的属性(如 historylocationmatch)。
//从 `react-router-dom` 中导入 `withRouter`,这个高阶组件用于将路由信息传递给目标组件
import { withRouter } from "react-router-dom";

//`MyComponent` 是一个普通的 React 类组件。通过 `withRouter` 包装后,它能够访问路由信息。
class MyComponent extends React.Component {
    render() {
        //使用 ES6 解构赋值语法,将 `history`、`location` 和 `match` 从组件的 `props` 中解构出来。
        //由于该组件被 `withRouter` 包装过,所以这三个属性会自动传递进来。
        //**`history`**:可以用来跳转页面,例如 `history.push("/new-url")`
        //**`location`**:包含有关当前 URL 的信息,例如当前路径 `location.pathname`
        //**`match`**:包含与当前路由匹配的参数和路径信息,例如路径中的动态参数
        
        const { history, location, match } = this.props;
        return <div>{location.pathname}</div>;
    }
}

export default withRouter(MyComponent);
//使用 `withRouter` 包装 `MyComponent` 组件并导出。通过 `withRouter`,`MyComponent` 
//能够访问路由属性(`history`、`location` 和 `match`),即使它并不是直接由路由渲染的。
  • 作用withRouterreact-router-dom 提供的高阶组件,用于将路由信息注入到不在路由组件树中的任意组件中。通常情况下,只有直接由路由渲染的组件(例如通过 <Route> 渲染的组件)才能访问路由的

    - 三个主要属性:historylocationmatch。如果某个组件不是通过路由渲染的(例如深层次的子组件),我们可以使用 withRouter 来为它注入这些路由相关的属性。

    - history:提供用于管理浏览历史的方法(如 pushreplace 等),可用于程序化导航。 - location:包含当前 URL 的信息(如 pathnamesearchhash 等),用于获取当前路径。

    - match:包含与当前 URL 相关的路由信息(如路径参数等)。

  • 使用场景withRouter 主要用于那些非路由组件,即不是直接通过 <Route> 渲染的组件,然而它们仍然需要访问路由相关的信息。例如:

    - 在深层嵌套组件中进行程序化导航。

    - 在非路由组件中根据当前 URL 或路由信息显示不同的内容。

    - 在组件中访问路径参数或查询参数,进行数据展示或逻辑判断。

    例子:在非路由组件中程序化导航

假设我们有一个按钮,当用户点击按钮时,跳转到一个新页面。通过 withRouter,我们可以在非路由组件中访问 history 来执行跳转。

import React from "react";
import { withRouter } from "react-router-dom";

class MyButton extends React.Component {
    handleClick = () => {
        // 程序化导航
        this.props.history.push("/new-page");
    };

    render() {
        return <button onClick={this.handleClick}>Go to new page</button>;
    }
}

export default withRouter(MyButton);

在这个例子中,MyButton 组件被 withRouter 包装,它可以通过 this.props.history 访问路由的 history 对象,使用 history.push("/new-page") 来进行导航。

  • 路由属性简介

    - history

    • push(path, [state]) :将新的条目推入浏览历史栈中,浏览器将导航到新的路径。
    • replace(path, [state]) :替换当前的历史记录条目,而不是推入新的条目。
    • goBack() :返回上一个历史条目。
    • goForward() :前进到下一个历史条目。

    - location

    • pathname:当前 URL 的路径部分,例如 /about
    • search:URL 中的查询字符串部分,例如 ?id=123
    • hash:URL 中的哈希部分,例如 #section1

    - match

    • params:包含路由参数,例如 /user/:id 中的 id
    • path:匹配的路由模板,例如 /user/:id
    • url:匹配的实际 URL,例如 /user/123

通过 withRouter,开发者可以方便地在非路由组件中获取路由信息并进行程序化导航。

2. connect(Redux)

  • 功能react-redux 中的 connect 函数,将 Redux 的状态和动作(actions)连接到一个 React 组件中,使组件能够读取 Redux 存储的状态并调度(dispatch)动作来修改状态。

connect 是 React-Redux 库中的一个高阶组件(HOC),用于将 Redux store 中的状态(state)和调度(dispatch)函数与 React 组件的 props 关联起来。

  • mapStateToProps:将 Redux store 中的状态映射到组件的 props
  • mapDispatchToProps:将 dispatch 方法映射到组件的 props,这样组件可以通过调用这些方法来触发 Redux actions,进而改变 Redux store 中的状态。
import { connect } from "react-redux";

// 将 Redux state 映射到组件的 props 中
const mapStateToProps = (state) => ({
    user: state.user, // 将 Redux 中的 user 状态映射到组件的 props 中
});

// 将 Redux actions 映射到组件的 props 中
const mapDispatchToProps = (dispatch) => ({
    login: (userData) => dispatch(loginAction(userData)), // 将 loginAction 这个 action 映射为组件中的 login 方法
});

class MyComponent extends React.Component {
    render() {
        const { user, login } = this.props; // 从 props 中获取 user 和 login
        return <div>{user.name}</div>; // 渲染 user 的 name 属性
    }
}

// 使用 connect 将 Redux 的 state 和 dispatch 绑定到组件 MyComponent 中
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

mapDispatchToProps 是一个函数,接收 Redux 的 dispatch 函数作为参数,并返回一个对象。该对象中的方法将作为组件的 props,用于调度(dispatch)Redux actions。

const mapDispatchToProps = (dispatch) => ({
    login: (userData) => dispatch(loginAction(userData)),
});

  • 它将一个 loginAction 动作(action)映射为组件的 login 方法,组件可以通过 this.props.login(userData) 来触发 loginAction

  • loginAction 是一个 Redux action,它通常是一个用于更新 Redux store 的函数,dispatch(loginAction(userData)) 将 action 传递给 Redux 来处理状态更新。

  • mapStateToProps:将 Redux 的状态作为 props 传递给组件。

  • mapDispatchToProps:将 Redux 的 action 作为 props 传递给组件,用于触发状态更新。

Redux 生态系统中的数据流

React 与 Redux 配合使用时,数据流是单向的,遵循以下步骤:

  1. Store:Redux store 保存整个应用程序的状态。state 是只读的,不能直接修改。
  2. Action:组件通过 dispatch(action) 发送动作(action 是一个描述发生了什么事件的普通对象)。
  3. Reducer:Redux 使用 reducer 来处理传递给它的 action,并返回新的 state。Reducer 是一个纯函数,它接收当前的 state 和 action,返回一个新的 state。
  4. State Updates:Redux store 更新之后,React 组件会重新渲染,从而反映最新的状态变化。

高阶组件(HOC)connect 的工作原理

connect 是 Redux 提供的高阶组件,它通过将 Redux store 的 statedispatch 映射到 React 组件的 props,使得组件能够读取 Redux 的状态和触发 Redux 的动作。

它背后的工作流程是:

  1. 订阅 Redux storeconnect 让组件订阅 Redux store,当 store 中的状态发生变化时,connect 会自动通知组件进行重新渲染。
  2. 传递 propsconnect 根据 mapStateToPropsmapDispatchToProps 将 Redux 的 statedispatch 映射为组件的 props
  3. 组件无感知 Redux:通过 connect,组件本身并不直接与 Redux store 交互,它只通过 props 接收数据和方法,保持了组件的可测试性和可维护性。

总结

  • connect 是 Redux 和 React 之间的桥梁,用于将 Redux 的状态和动作绑定到 React 组件。
  • mapStateToProps 将 Redux store 的状态映射为组件的 props
  • mapDispatchToProps 将 Redux action 映射为组件的 props,使得组件可以通过调用这些方法触发状态更新。
  • 这种模式使得 React 组件能够从 Redux store 中读取数据,并通过 dispatch 触发状态的变化,实现了状态的集中管理和组件的分离式开发。

3. withTheme(Material-UI)

  • 功能:将 Material-UI(现在称为 MUI)中的 withTheme 高阶组件(HOC),将主题(theme)对象注入到 MyComponent 组件的 props 中,从而使组件能够访问 MUI 提供的主题配置,并基于该主题来定制组件的样式。。
import { withTheme } from "@material-ui/core/styles";

const MyComponent = (props) => {
    const { theme } = props; // 从 props 中获取注入的 theme 对象
    return <div style={{ color: theme.palette.primary.main }}>Hello</div>; // 使用 theme 对象来设定样式
};

export default withTheme(MyComponent); // 使用 withTheme 高阶组件包装 MyComponent

withTheme 的作用

withTheme 是 MUI 中的一个高阶组件(HOC),用于将全局的主题对象注入到一个没有直接访问主题的组件中。通过 withTheme,你可以在组件内部访问当前的主题,并根据主题的颜色、字体、间距等配置来动态调整组件的样式。

在上面的代码中:

  • withTheme:将全局的 theme 对象注入到 MyComponentprops 中。
  • theme.palette.primary.main:通过 theme 对象访问主题的颜色配置,palette.primary.main 通常表示主题中的主色(primary color)。
  • 动态样式:组件的 div 元素的 style 属性使用了 theme.palette.primary.main,因此它的字体颜色会根据当前主题的主色来变化。

MUI 主题(Theme)系统简介

Material-UI 提供了一个强大的主题系统,允许开发者通过一个全局的 theme 对象来统一管理应用程序的样式。这个主题对象可以包含颜色、字体、间距、断点(媒体查询)等配置项。

常见的 theme 属性:

  • palette:管理颜色。比如 theme.palette.primary.main 是主色,theme.palette.secondary.main 是副色。
  • typography:管理字体。比如 theme.typography.h1 表示一级标题的样式。
  • spacing:管理间距。可以通过 theme.spacing(n) 设置组件的内外边距。
  • breakpoints:管理响应式设计的断点。用于根据设备屏幕宽度自动调整组件样式。
const theme = {
  palette: {
    primary: {
      main: '#1976d2',  // 主色为蓝色
    },
    secondary: {
      main: '#dc004e',  // 副色为红色
    },
  },
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
    h1: {
      fontSize: '2rem',
    },
  },
  spacing: 8, // 设置间距单位为 8px
};

withTheme 的使用场景

withTheme 通常用于需要访问 MUI 全局主题对象的函数组件或类组件中。虽然 MUI 的大部分组件可以通过 useTheme 钩子直接访问主题,但在某些场景下(比如类组件中),使用 withTheme 是一种更灵活的方式。

场景一:基于主题动态调整样式

开发者可以根据不同的主题颜色或者断点来调整组件的样式。例如,调整组件的字体颜色或背景颜色,使其符合全局主题的配色风格。

const MyComponent = (props) => {
    const { theme } = props;
    return (
        <div style={{ backgroundColor: theme.palette.secondary.light }}>
            Welcome to My App
        </div>
    );
};

export default withTheme(MyComponent);

场景二:类组件中使用 withTheme

在类组件中,不能直接使用 useTheme 钩子,这时就可以使用 withTheme 高阶组件。

class MyClassComponent extends React.Component {
    render() {
        const { theme } = this.props;
        return (
            <div style={{ color: theme.palette.primary.dark }}>
                Hello from class component
            </div>
        );
    }
}

export default withTheme(MyClassComponent);

与 MUI 主题系统的衔接

在 MUI 中,主题系统是应用样式的核心部分,withTheme 提供了访问这个主题的能力,使开发者能够:

  • 统一样式:通过访问主题,可以确保所有组件的样式在颜色、字体、间距等方面保持一致性。
  • 响应式设计:通过主题的 breakpoints 属性,可以根据不同的设备大小自适应调整组件样式。
  • 可定制性:通过访问和修改 theme,开发者可以轻松地定制整个应用的外观,而不需要逐个修改组件的样式。

总结

  • withTheme 是 Material-UI 中的一个高阶组件,用于将全局主题对象注入到组件中。
  • 通过 withTheme,组件可以访问主题的颜色、字体、间距等配置,方便根据主题调整样式。
  • withTheme 适用于函数组件和类组件,尤其是在类组件中使用时,它提供了访问主题的简便方式。
  • Material-UI 的主题系统允许开发者全局管理样式,withTheme 提供了一种灵活访问主题的途径,确保应用的样式一致性。

4. withStyles(Material-UI)

  • 功能:将Material-UI 的 withStyles 高阶组件(HOC),用于为 MyComponent 组件添加自定义的样式。withStyles 可以将定义好的样式对象传递给组件,使组件通过 props 的形式接收到这些样式,并应用到相应的元素上。。
import { withStyles } from "@material-ui/core/styles";

// 定义样式对象
const styles = {
    root: {
        background: "lightblue",  // 设置背景颜色为浅蓝色
    },
};

// 函数组件,接收 props,并从中解构出 classes
const MyComponent = (props) => {
    const { classes } = props;  // 从 props 中获取 classes 对象,该对象包含所有样式类名
    return <div className={classes.root}>Styled Component</div>;  // 将样式类名应用到 div 上
};

// 使用 withStyles 高阶组件包装 MyComponent
export default withStyles(styles)(MyComponent);


withStyles 的作用

withStyles 是 Material-UI 中的一个高阶组件,它的主要功能是将自定义的样式对象传递给组件,并将这些样式注入到组件的 props 中。在上面的代码中,withStyles(styles)styles 对象传递给 MyComponent,并将 styles 对象中的类名通过 props 中的 classes 属性传递给组件。

  • styles 对象:定义了一个简单的样式,root 类名中的背景颜色被设定为浅蓝色。
  • classes 对象withStyles 会自动为样式生成类名,并通过 classes 属性传递给组件。开发者可以通过 classes.root 来访问定义的 root 类。

在组件渲染时,<div> 元素的 className 会被赋值为 classes.root,从而应用定义的样式。

withStyles 的使用方式

1. 基础样式定义

在最简单的用法中,withStyles 接收一个样式对象,其中定义的属性可以作为类名使用。每个类名对应的样式规则会通过 classes 对象传递给组件。

const styles = {
    root: {
        background: "lightblue",
        padding: "20px",
    },
    title: {
        fontSize: "24px",
        fontWeight: "bold",
    },
};

const MyComponent = (props) => {
    const { classes } = props;
    return (
        <div className={classes.root}>
            <h1 className={classes.title}>Styled Component</h1>
        </div>
    );
};

export default withStyles(styles)(MyComponent);

在这个例子中,roottitle 样式分别应用到了 <div><h1> 元素中。

2. 动态样式

样式对象中的值可以根据组件的 props 动态生成。比如,可以根据 props 来设置不同的背景颜色。

const styles = (theme) => ({
    root: {
        background: (props) => (props.primary ? theme.palette.primary.main : "lightblue"),
        padding: "20px",
    },
});

const MyComponent = (props) => {
    const { classes } = props;
    return <div className={classes.root}>Styled Component</div>;
};

export default withStyles(styles)(MyComponent);

在这个例子中,如果组件的 props.primarytrue,则背景颜色会使用 Material-UI 主题的主色,否则为浅蓝色。

3. 访问 Material-UI 主题

通过 withStyles,你可以轻松地在样式中访问 Material-UI 提供的全局 theme,从而使用主题中的颜色、字体、间距等配置。

const styles = (theme) => ({
    root: {
        background: theme.palette.background.default,  // 使用主题的背景颜色
        color: theme.palette.text.primary,  // 使用主题的主文本颜色
        padding: theme.spacing(2),  // 使用主题的间距配置
    },
});

const MyComponent = (props) => {
    const { classes } = props;
    return <div className={classes.root}>Styled Component</div>;
};

export default withStyles(styles)(MyComponent);

withStyles 的应用场景

withStyles 通常用于以下场景:

  1. 将样式与组件逻辑分离:通过 withStyles,可以将样式抽离到一个单独的对象中,使代码的逻辑和样式更清晰地分离。
  2. 使用全局主题withStyles 提供了一种访问 Material-UI 主题的简便方式,确保组件样式与全局主题一致。
  3. 动态样式:可以根据组件的 props 动态生成样式,从而实现更灵活的 UI。
  4. 复用样式:通过 withStyles,可以为多个组件应用相同的样式规则,增强代码的复用性。

友情赞助
大家有空闲,帮忙试用下,国货抖音出品的AI编程代码插件,比比看GitHub Copilot那个好**^-^**
(适用JetBrains全家桶系列、Visual Studio家族IDE工具安装等主流IDE工具,支持100+种编程语言)
帮忙去助力>>