9

十一、组合 VS 继承

React具有强大的组合模式,我们建议使用组合而不是继承来重用组件之间的代码。

在本节中,我们将考虑一些新的React常常遇到的开发继承的问题,并展示如何使用组合来解决它们。

有容乃大

一些组件提前不知道它们的子组件是什么的。 这对于像SidebarDialog这样的代表通用“框”的组件是特别常见的。
我们建议这些组件使用特殊的children属性将子组件元素直接传递到他们中:

function FancyBorder(props) {
    return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
            {props.children}
        </div>
    );
}

这让其他组件通过嵌套JSX传递任意到他们的children中:

function WelcomeDialog(props) {
    return (
        <FancyBorder color="blue">
            <h1 className="Dialog-title">
                Welcome
            </h1>
            <p className="Dialog-message">
                感谢参观鹏寰国际大厦
            </p>
        </FancyBorder>
    );
}

最终的html结构为:

<div id="root">
    <div data-reactroot="" class="FancyBorder FancyBorder-blue">
        <h1 class="Dialog-title">Welcome</h1>
        <p class="Dialog-message">感谢参观鹏寰国际大厦</p>
    </div>
</div>

任何在<FancyBorder>JSX标签内部的东西都会作为children props被传递到FancyBorder组件中。 由于FancyBorder<div>中渲染{props.children},所以所传递的元素将显示在这个div当中。

虽然这不常见,有时你可能需要在组件中有多个“窟窿”。 在这种情况下,你可以提出自己的规范,而不是使用children

import React from 'react';
import ReactDOM from 'react-dom';

function Contacts() {
    return <h1>tel:182012322**</h1>
}
function Chat() {
    return <span>chat content</span>
}

function SplitPanel(props) {
    return (
        <div className="SplitPane">
            <div className="SplitPane-left">
                {props.left}
            </div>
            <div className="SplitPane-right">
                {props.right}
            </div>
        </div>
    )
}

function App() {
    return (
        <SplitPanel left={<Contacts />} right={<Chat />}/>
    );
}
ReactDOM.render(
    <App />,
    document.getElementById('root')
);

<Contacts /><Chat />等React元素只是一个对象,所以你可以像任何其他数据一样把它们当做props去传递。

用的专业一点

有时我们认为某个组件是其他组件的“special cases”。 例如,我们可能会说WelcomeDialogDialog的一个“special cases”

在React中,这也通过组合实现,其中更“特殊”的组件渲染更“通用”的组件并用props配置它:

function Dialog(props) {
    return (
        <FancyBorder color="blue">
            <h1 className="Dialog-title">
                {props.title}
            </h1>
            <p className="Dialog-message">
                {props.message}
            </p>
        </FancyBorder>
    );
}

function WelcomeDialog(props) {
    return (
        <Dialog title="Welcome" message="欢迎参观鹏寰国际大厦" />
    );
}

组合对于定义为类的组件同样有效:

import React from 'react';
import ReactDOM from 'react-dom';

function FancyBorder(props) {
    return (
        <div className={'FancyBorder FancyBorder-' + props.color}>
            {props.children}
        </div>
    );
}

function Dialog(props) {
    return (
        <FancyBorder color="blue">
            <h1 className="Dialog-title">
                {props.title}
            </h1>
            <p className="Dialog-message">
                {props.message}
            </p>
            {props.children}
        </FancyBorder>
    );
}

class SignUpDialog extends React.Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleSignUp = this.handleSignUp.bind(this);
        this.state = {login: ''};
    }

    render() {
        return (
            <Dialog title="Mars Exploration Program"
                    message="How should we refer to you?">
                <input value={this.state.login}
                       onChange={this.handleChange}/>
                <button onClick={this.handleSignUp}>
                    Sign Me Up!
                </button>
            </Dialog>
        );
    }

    handleChange(e) {
        this.setState({login: e.target.value});
    }

    handleSignUp() {
        alert(`Welcome aboard, ${this.state.login}!`);
    }
}

ReactDOM.render(
    <SignUpDialog />,
    document.getElementById('root')
);

关于继承

在Facebook,使用了React在数千个组件,他们没有发现一个必须实现组件继承层次结构的用例。

props和组合给你所有的灵活性,你需要以一个明确和安全的方式自定义组件的外观和行为。 请记住,组件可以接受任意props,包括原始值,React元素或函数。

如果要在组件之间重用非UI功能,建议您将其提取到单独的JavaScript模块中。 组件可以导入它并使用该函数,对象或类,而不扩展它。


张亚涛
5.3k 声望2.8k 粉丝

人首先应该接受现实,承认问题的存在,并且反思它。而不是首先就跳出来回避这个问题,找比我们更差的。质疑甚至抨击提出问题的人,并且一杆子打死。