十三、组合vs继承
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。
13.1 理解组件化
组件化是React的核心思想:
-
组件化提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
-
任何的应用都会被抽象成一颗组件树。
组件化思想的应用:
-
有了组件化的思想,我们在之后的开发中就要充分的利用它。
-
尽可能的将页面拆分成一个个小的、可复用的组件。
-
这样让我们的代码更加方便组织和管理,并且扩展性也更强。
React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类 组件:
- 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
vue 中有没有类组件和函数式组件?vue2中有
<template> </template> <script> export default { props:[], data () { return {} } } </script>
vue中的函数式组件 - 无状态组件,所有的数据来源均来自父组件
{ {props.msg}}vue 中的类组件 - 兼容ts时
hello vueclass Home extends Vue {} // export default {}
-
根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component )和有状态组件(Stateful Component);
-
根据组件的不同职责,可以分成:展示型组件(Presentational Component - 只做数据的展示,一般不需要写更多的业务逻辑-数据请求不出现在展示型组件-顶多发出请求的指令-具体的请求交给容器型组件)和容器型组件(Container Component - 负责给展示性组件提供数据以及处理展示型组件需要的具体的业务逻辑) - 状态管理器-更容易理解;
这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
- 函数组件、无状态组件、展示型组件主要关注UI的展示;
- 类组件、有状态组件、容器型组件主要关注数据逻辑;
13.2 使用组合而非继承实现React组件化
有些组件无法提前知晓它们子组件的具体内容,建议这些组件使用一个特殊的 children
prop 来将他们的子组件传递到渲染结果中。
参照5.3章节
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children
,而是自行约定:将所需内容传入 props,并使用相应的 prop。
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
// 引入react组件时,后缀名可以不写
import App from './08_com/01_App_props_slot'
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
src/08_com/01_App_props_slot.jsx
// src/08_com/01_App_props_slot.jsx
import { Component } from "react";
class Header extends Component {
render () {
console.log(this.props)
return (
<ul>
<li>左侧: { this.props.children[0] }</li>
<li>中间: { this.props.children[1] }</li>
<li>右侧: { this.props.children[2] }</li>
</ul>
)
}
}
class MyHeader extends Component {
render () {
return (
<>
<ul>
<li>{ this.props.left }</li>
<li>{ this.props.default }</li>
<li>{ this.props.right }</li>
</ul>
</>
)
}
}
class App extends Component {
render () {
return (
<div>
<Header>
<div>城市</div>
<div>搜索</div>
<div>登录</div>
</Header>
<hr />
<Header>
<div>返回</div>
<div>标题</div>
<div>更多</div>
</Header>
<hr />
<MyHeader
left={ <>返回</> }
default={ <>我的标题</> }
right={ <>我的右侧</> }
>
1234567
</MyHeader>
</div>
)
}
}
export default App
像 App组件中的
left
和default
以及 right 的 属性对应的之类的React
元素本质就是对象(object
),所以你可以把它们当作props
,像其他数据一样传递。这种方法可能使你想起vue中“插槽”(slot
)的概念,但在React
中没有“插槽”这一概念的限制,你可以将任何东西作为 props 进行传递。
13.3 封装Modal弹窗
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
// 引入react组件时,后缀名可以不写
// import App from './08_com/01_App_props_slot'
import App from './08_com/02_App_modal'
const root = ReactDOM.createRoot(document.querySelector('#root'))
root.render(<App />)
src/08_com/02_App_modal.jsx
// src/08_com/02_App_modal.jsx
import React, { Component } from 'react';
class Modal extends Component {
render () {
return (
<div
style={
{
width: '100%',
minHeight: 600,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)'
}}
>
<div style={
{ width: 600, height: 300, backgroundColor: '#fff' }}>
这里是模态框
<button onClick={ () => this.props.onClick() }>关闭</button>
</div>
</div>
)
}
}
export default class App extends Component {
state = { flag: false }
render() {
return (
<div>
<button onClick={ () => this.setState({ flag: true })}>打开</button>
{ this.state.flag ? <Modal onClick={ () => this.setState({ flag: false })}/> : null }
</div>
);
}
}
审查元素发现 Modal 组件是渲染在原来的组件的位置的,如果想要让它渲染到不同的位置怎么办呢?
13.4 ReactDOM.createPortal()
普通的组件,子组件的元素将挂载到父组件的DOM节点中。
有时需要将元素渲染到DOM中的不同位置上去,这是就用到的portal的方法。
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from &#