1. 首页

React组件复用指南

React组件复用指南

本文来源于2篇文章:

  • 高阶组件: 来源于franleplant的文章React Higher Order Components in depth,我们对少部分内容做了删减和修改
  • 渲染属性: 来源于Michael Jackson(是的,franleplant的文章中就提到了此人)的文章Use a Render Prop!,react官网关于Render Prop的部分就是他的pr,此文包含了大量作者的主观观点,我们会在后文进行相对客观的比较.

高阶组件(HOC)

什么是高阶组件?

高阶组件就是一个 React 组件包裹着另外一个 React 组件

这种模式通常使用函数来实现,基本上是一个类工厂(是的,一个类工厂!),它的函数签名可以用类似 haskell 的伪代码表示

hocFactory:: W: React.Component => E: React.Component

其中 W (WrappedComponent) 指被包裹的 React.Component,E (EnhancedComponent) 指返回类型为 React.Component 的新的 HOC。

我们有意模糊了定义中“包裹”的概念,因为它可能会有以下两种不同的含义之一:

  1. 属性代理(Props Proxy): HOC 对传给 WrappedComponent W 的 porps 进行操作,
  2. 反向继承(Inheritance Inversion): HOC 继承 WrappedComponent W。

我们会深入地探究这两种模式。

HOC 工厂的实现方法

这一节我们将会研究 React 中两种 HOC 的实现方法:Props Proxy (PP) and Inheritance Inversion (II)。两种方法都可以操作 WrappedComponent。

属性代理(Props Proxy)

Props Proxy (PP) 的最简实现:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

这里主要是 HOC 在 render 方法中 返回 了一个 WrappedComponent 类型的 React Element。我们还传入了 HOC 接收到的 props,这就是名字 Props Proxy 的由来。

使用 Props Proxy 可以做什么?

  • 操作 props
  • 通过 Refs 访问到组件实例
  • 提取 state
  • 用其他元素包裹 WrappedComponent

操作 props

你可以读取、添加、编辑、删除传给 WrappedComponent 的 props。

当删除或者编辑重要的 props 时要小心,你可能应该通过命名空间确保高阶组件的 props 不会破坏 WrappedComponent

例子:添加新的 props。在这个应用中,当前登录的用户可以在 WrappedComponent 中通过 this.props.user 访问到。

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

通过 Refs 访问到组件实例

你可以通过引用ref)访问到 this (WrappedComponent 的实例),但为了得到引用,WrappedComponent 还需要一个初始渲染,意味着你需要在 HOC 的 render 方法中返回 WrappedComponent 元素,让 React 开始它的一致化处理,你就可以得到 WrappedComponent的实例的引用。

例子:如何通过 refs 访问到实例的方法和实例本身:

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}/>
    }
  }
}

Ref 的回调函数会在 WrappedComponent 渲染时执行,你就可以得到 WrappedComponent 的引用。这可以用来读取/添加实例的 props ,调用实例的方法。

提取 state

你可以通过传入 props 和回调函数把 state 提取出来,类似于 smart component 与 dumb component。

提取 state 的例子:提取了 input 的 value 和 onChange 方法。这个简单的例子不是很常规,但足够说明问题。

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}/>
  }
}

这个 input 会自动成为受控input

更多关于常规的双向绑定 HOC 请点击 链接 用其他元素包裹 WrappedComponent

为了封装样式、布局或别的目的,你可以用其它组件和元素包裹 WrappedComponent。基本方法是使用父组件(附录 B)实现,但通过 HOC 你可以得到更多灵活性。

例子:包裹样式

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return (
        <div style={{display: 'block'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

反向继承(Inheritance Inversion)

Inheritance Inversion (II) 的最简实现:

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

你可以看到,返回的 HOC 类(Enhancer)继承了 WrappedComponent。之所以被称为 Inheritance Inversion 是因为 WrappedComponent 被 Enhancer 继承了,而不是 WrappedComponent 继承了 Enhancer。在这种方式中,它们的关系看上去被反转(inverse)了。

Inheritance Inversion 允许 HOC 通过 this 访问到 WrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法

关于生命周期方法可以用来做什么,我不想细说,因为它是 React 的特性而不是 HOC 的特性。但请注意通过 II 你可以创建新的生命周期方法。为了不破坏 WrappedComponent,记得调用 super.[lifecycleHook]

一致化处理(Reconciliation process)

开始之前我们先理清一些概念。

React 元素决定描述了在 React 执行一致化处理时它要渲染什么。

React 元素有两种类型:字符串和函数。字符串类型的 React 元素代表 DOM 节点,函数类型的 React 元素代表继承 React.Component 的组件。更多关于元素(Element)和组件(Component)请看这篇文章。函数类型的 React 元素会在一致化处理中被解析成一个完全由字符串类型 React 组件组成的树(而最后的结果永远是 DOM 元素)。

这很重要,意味着 Inheritance Inversion 的高阶组件不一定会解析完整子树

Inheritance Inversion 的高阶组件不一定会解析完整子树

这在学习渲染劫持(Render Highjacking)时非常重要。

你可以用 Inheritance Inversion 做什么?

  • 渲染劫持(Render Highjacking)
  • 操作 state

渲染劫持

之所以被称为渲染劫持是因为 HOC 控制着 WrappedComponent 的渲染输出,可以用它做各种各样的事。

通过渲染劫持你可以:

  • 在由 render输出的任何 React 元素中读取、添加、编辑、删除 props
  • 读取和修改由 render 输出的 React 元素树
  • 有条件地渲染元素树
  • 把样式包裹进元素树(就像在 Props Proxy 中的那样)

*render 指 WrappedComponent.render 方法

不能编辑或添加 WrappedComponent 实例的 props,因为 React 组件不能编辑它接收到的 props,但你可以修改由 render 方法返回的组件的 props。 就像我们刚才学到的,II 类型的 HOC 不一定会解析完整子树,意味着渲染劫持有一些限制。根据经验,使用渲染劫持你可以完全操作 WrappedComponent 的 render 方法返回的元素树。但是如果元素树包括一个函数类型的 React 组件,你就不能操作它的子组件了。(被 React 的一致化处理推迟到了真正渲染到屏幕时)

例1:条件渲染。当 this.props.loggedIn 为 true 时,这个 HOC 会完全渲染 WrappedComponent的渲染结果。(假设 HOC 接收到了 loggedIn 这个 prop)

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}

例2:修改由 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
    }
  }
}

在这个例子中,如果 WrappedComponent 的输出在最顶层有一个 input,那么就把它的 value 设为 “may the force be with you”

你可以在这里做各种各样的事,你可以遍历整个元素树,然后修改元素树中任何元素的 props。这也正是样式处理库 Radium 所用的方法(案例分析一节中有更多关于 Radium 的信息)。

注:在 Props Proxy 中不能做到渲染劫持。 虽然通过 WrappedComponent.prototype.render 你可以访问到 render 方法,不过还需要模拟 WrappedComponent 的实例和它的 props,还可能亲自处理组件的生命周期,而不是交给 React。根据我的实验,这么做不值,你要是想做到渲染劫持你应该用 Inheritance Inversion 而不是 Props Proxy。记住,React 在内部处理了组件实例,你处理实例的唯一方法是通过 this 或者 refs。

操作 state

HOC 可以读取、编辑和删除 WrappedComponent 实例的 state,如果你需要,你也可以给它添加更多的 state。记住,这会搞乱 WrappedComponent 的 state,导致你可能会破坏某些东西。要限制 HOC 读取或添加 state,添加 state 时应该放在单独的命名空间里,而不是和 WrappedComponent 的 state 混在一起。

例子:通过访问 WrappedComponent 的 props 和 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>
      )
    }
  }
}

这里 HOC 用其他元素包裹着 WrappedComponent,还输出了 WrappedComponent 实例的 props 和 state。JSON.stringify 的小技巧是由 Ryan Florence 和 Michael Jackson 教我的。这个调试器完整的实现在这里

命名

用 HOC 包裹了一个组件会使它失去原本 WrappedComponent 的名字,可能会影响开发和调试。

通常会用 WrappedComponent 的名字加上一些 前缀作为 HOC 的名字。下面的代码来自 React-Redux:

HOC.displayName = `HOC(${getDisplayName(WrappedComponent)})`
//或
class HOC extends ... {
  static displayName = `HOC(${getDisplayName(WrappedComponent)})`
  ...
}

getDisplayName 函数:

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName ||
         WrappedComponent.name ||
         ‘Component’
}

实际上你不用自己写,recompose 提供了这个函数。

案例分析

React-Redux

React-Redux 是 Redux 官方的 React 绑定实现。他提供的函数中有一个 connect,处理了监听 store 和后续的处理。是通过 Props Proxy 来实现的。

在纯的Flux架构中,React 组件会连接到一个或多个 store,需要大量添加和删除 store 监听器,挑出 state 中 需要的部分。React-Redux 的实现非常好,它把这些处理都抽象出来了。总的说来,你不用再自己写了。

Radium

Radium 通过在内联样式中使用CSS 伪类增强了内联样式的能力。内联样式为什么好是另一个话题,很多人已经开始这样做,像 Radium 这样的库真的简化了这个过程。如果你想了解更多关于内联样式请参考 Vjeux 的这个 ppt。

话说回来,Radium 是怎样做到内联 CSS 伪类的,比如 hover?它用 Inheritance Inversion 模式做到了渲染劫持,插入对应的事件监听器来模拟 CSS 伪类,比如 hover。事件监听器插入到了 React 组件的 props 里。Radium 需要读取 WrappedComponent 的 render 方法输出的所有组件树,每当它发现一个新的带有 style 属性的组件时,在 props 上添加一个事件监听器。简单地说,Radium 修改了组件树的 props(实际上 Radium 的实现会更复杂些,你理解意思就行)。

Radium 暴露的 API 真的很简单。令人印象深刻的是,他在用户甚至没有察觉到的时候,完成了所有工作。由此可见 HOC 的威力。

附录 A: HOC 和参数

你可以选择跳过下面的内容

有时,在你的 HOC 上使用参数是很有用的。这对中级以上的 JS 开发者来说是很自然的事,但上面的例子都没有用到,为了做到详尽无遗我们快速地讲解一下。

例子:Props Proxy 模式 的 HOC 最简参数使用方法。关键在于 HOCFactoryFactory 函数。

function HOCFactoryFactory(...params){
  // do something with params
  return function HOCFactory(WrappedComponent) {
    return class HOC extends React.Component {
      render() {
        return <WrappedComponent {...this.props}/>
      }
    }
  }
}

你可以这样用:

HOCFactoryFactory(params)(WrappedComponent)
//或
@HOCFatoryFactory(params)
class WrappedComponent extends React.Component{}

附录 B: 与父组件的不同

你可以选择跳过下面的内容

父组件就是有一些子组件的 React 组件。React 有访问和操作子组件的 API。

例子:父组件访问子组件。

class Parent extends React.Component {
    render() {
      return (
        <div>
          {this.props.children}
        </div>
      )
    }
}
render((
  <Parent>
    {children}
  </Parent>
  ), mountNode)

相对 HOC 来说,父组件可以做什么,不可以做什么?我们详细地总结一下:

  • 渲染劫持 (在 Inheritance Inversion 一节讲到)
  • 操作内部 props (在 Inheritance Inversion 一节讲到)
  • 提取 state。但也有它的不足。只有在显式地为它创建钩子函数后,你才能从父组件外面访问到它的 props。这给它增添了一些不必要的限制。
  • 用新的 React 组件包裹。这可能是唯一一种父组件比 HOC 好用的情况。HOC 也可以做到。
  • 操作子组件会有一些陷阱。例如,当子组件没有单一的根节点时,你得添加一个额外的元素包裹所有的子组件,这让你的代码有些繁琐。在 HOC 中单一的根节点会由 React/JSX语法来确保。
  • 父组件可以自由应用到组件树中,不像 HOC 那样需要给每个组件创建一个类。

一般来讲,可以用父组件的时候就用父组件,它不像 HOC 那么 hacky,但也失去了 HOC 可以提供的灵活性。

渲染属性(Render Props)

Mixins 存在的问题

我的演讲始于高阶组件主要解决的问题:代码复用

让我们回到 2015 年使用 React.createClass 那会儿。假定你现在有一个简单的 React 应用需要跟踪并在页面上实时显示鼠标位置。你可能会构建一个下面这样的例子:

import React from 'react'
import ReactDOM from 'react-dom'
const App = React.createClass({
  getInitialState() {
    return { x: 0, y: 0 }
  },
  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  },
  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'))

现在,假定我们在另一个组件中也需要跟踪鼠标位置。我们可以重用 <App> 中的代码吗?

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'))

问题解决了,对吧?现在,任何人都能轻松地将 MouseMixin 混入他们的组件中,并通过 this.state 属性获得鼠标的 xy 坐标。

HOC 是新的 Mixin

去年,随着ES6 class 的到来,React 团队最终决定使用 ES6 class 来代替 createClass。这是一个明智的决定,没有人会在 JavaScript 都内置了 class 时还会维护自己的类模型。

就存在一个问题:ES6 class 不支持 mixin。除了不是 ES6 规范的一部分,Dan 已经在一篇 React 博客上发布的博文上详细讨论了 mixin 存在的其他问题。

minxins 的问题总结下来就是

  • ES6 class。其不支持 mixins。
  • 不够直接。minxins 改变了 state,因此也就很难知道一些 state 是从哪里来的,尤其是当不止存在一个 mixins 时。
  • 名字冲突。两个要更新同一段 state 的 mixins 可能会相互覆盖。createClass API 会对两个 mixins 的 getInitialState 是否具有相同的 key 做检查,如果具有,则会发出警告,但该手段并不牢靠。

所以,为了替代 mixin,React 社区中的不少开发者最终决定用高阶组件(简称 HOC)来做代码复用。在这个范式下,代码通过一个类似于 装饰器(decorator) 的技术进行共享。首先,你的一个组件定义了大量需要被渲染的标记,之后用若干具有你想用共享的行为的组件包裹它。因此,你现在是在 装饰 你的组件,而不是混入你需要的行为!

import React from 'react'
import ReactDOM from 'react-dom'
const withMouse = (Component) => {
  return class extends React.Component {
    state = { x: 0, y: 0 }
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
    render() {
      return (
        <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
          <Component {...this.props} mouse={this.state}/>
        </div>
      )
    }
  }
}
//码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/
const App = React.createClass({
  render() {
    // 现在,我们得到了一个鼠标位置的 prop,而不再需要维护自己的 state
    const { x, y } = this.props.mouse
    return (
      <div style={{ height: '100%' }}>
        <h1>The mouse position is ({x}, {y})</h1>
      </div>
    )
  }
})
// 主需要用 withMouse 包裹组件,它就能获得 mouse prop
const AppWithMouse = withMouse(App)
ReactDOM.render(<AppWithMouse/>, document.getElementById('app'))

让我们和 mixin 说再见,去拥抱 HOC 吧。

在 ES6 class 的新时代下,HOC 的确是一个能够优雅地解决代码重用问题方案,社区也已经广泛采用它了。

此刻,我想问一句:是什么驱使我们迁移到 HOC ? 我们是否解决了在使用 mixin 时遇到的问题?

让我们看下:

  • ES6 class。这里不再是问题了,ES6 class 创建的组件能够和 HOC 结合。
  • 不够直接。即便用了 HOC,这个问题仍然存在。在 mixin 中,我们不知道 state 从何而来,在 HOC 中,我们不知道 props 从何而来。
  • 名字冲突。我们仍然会面临该问题。两个使用了同名 prop 的 HOC 将遭遇冲突并且彼此覆盖,并且这次问题会更加隐晦,因为 React 不会在 prop 重名是发出警告。

另一个 HOC 和 mixin 都有的问题就是,二者使用的是 静态组合 而不是 动态组合。问问你自己:在 HOC 这个范式下,组合是在哪里发生的?当组件类(如上例中的的 AppWithMouse)被创建后,发生了一次静态组合。

你无法在 render 方法中使用 mixin 或者 HOC,而这恰是 React 动态 组合模型的关键。当你在 render 中完成了组合,你就可以利用到所有 React 生命期的优势了。动态组合或许微不足道,但兴许某天也会出现一篇专门探讨它的博客,等等,我有点离题了。

总而言之:使用 ES6 class 创建的 HOC 仍然会遇到和使用 createClass 时一样的问题,它只能算一次重构。现在不要说拥抱 HOC 了,我们不过在拥抱新的 mixin!

除了上述缺陷,由于 HOC 的实质是包裹组件并创建了一个混入现有组件的 mixin 替代,因此,HOC 将引入大量的繁文缛节。从 HOC 中返回的组件需要表现得和它包裹的组件尽可能一样(它需要和包裹组件接收一样的 props 等等)。这一事实使得构建健壮的 HOC 需要大量的样板代码(boilerplate code)。

Render Props

我会这么定义 render prop:

一个 render prop 是一个类型为函数的 prop,它让组件知道该渲染什么。

更通俗的说法是:不同于通过 “混入” 或者装饰来共享组件行为,一个普通组件只需要一个函数 prop 就能够进行一些 state 共享

继续到上面的例子,我们将通过一个类型为函数的 render 的 prop 来简化 withMouse HOC 到一个普通的 <Mouse> 组件。然后,在 <Mouse>render 方法中,我们可以使用一个 render prop 来让组件知道如何渲染:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
// 与 HOC 不同,我们可以使用具有 render prop 的普通组件来共享代码
class Mouse extends React.Component {
  static propTypes = {
    render: PropTypes.func.isRequired
  }
  //码农进阶题库,每天一道面试题 or Js小知识 https://www.javascriptc.com/interview-tips/
  state = { x: 0, y: 0 }
  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    })
  }
  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    )
  }
}
const App = React.createClass({
  render() {
    return (
      <div style={{ height: '100%' }}>
        <Mouse render={({ x, y }) => (
          // render prop 给了我们所需要的 state 来渲染我们想要的
          <h1>The mouse position is ({x}, {y})</h1>
        )}/>
      </div>
    )
  }
})
ReactDOM.render(<App/>, document.getElementById('app'))

这里需要明确的概念是,<Mouse> 组件实际上是调用了它的 render 方法来将它的 state 暴露给 <App> 组件。因此,<App> 可以随便按自己的想法使用这个 state,这太美妙了。

在此,我想说明,“children as a function” 是一个 完全相同的概念,只是用 children prop 替代了 render prop。我挂在嘴边的 render prop 并不是在强调一个 名叫 prop 的 prop,而是在强调你使用一个 prop 去进行渲染的概念。

该技术规避了所有 mixin 和 HOC 会面对的问题:

  • ES6 class。不成问题,我们可以在 ES6 class 创建的组件中使用 render prop。
  • 不够直接。我们不必再担心 state 或者 props 来自哪里。我们可以看到通过 render prop 的参数列表看到有哪些 state 或者 props 可供使用。
  • 名字冲突。现在不会有任何的自动属性名称合并,因此,名字冲突将全无可乘之机。

并且,render prop 也不会引入 任何繁文缛节,因为你不会 包裹装饰 其他的组件。它仅仅是一个函数!如果你使用了 TypeScript 或者 Flow,你会发现相较于 HOC,现在很容易为你具有 render prop 的组件写一个类型定义。当然,这是另外一个话题了。

另外,这里的组合模型是 动态的!每次组合都发生在 render 内部,因此,我们就能利用到 React 生命周期以及自然流动的 props 和 state 带来的优势。

使用这个模式,你可以将 任何 HOC 替换一个具有 render prop 的一般组件。这点我们可以证明!😅

Render Props > HOCs

一个更将强有力的,能够证明 render prop 比 HOC 要强大的证据是,任何 HOC 都能使用 render prop 替代,反之则不然。下面的代码展示了使用一个一般的、具有 render prop 的 <Mouse> 组件来实现的 withMouse HOC:

const withMouse = (Component) => {
  return class extends React.Component {
    render() {
      return <Mouse render={mouse => (
        <Component {...this.props} mouse={mouse}/>
      )}/>
    }
  }
}

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:React组件复用指南

链接:https://www.javascriptc.com/3236.html

« 如何使用 GraphQL 构建 TypeScript+React 应用
前端性能优化-执行篇»
Flutter 中文教程资源

相关推荐

QR code