如何在React中组织代码以复用逻辑

本文探讨了在React中组织代码以复用逻辑的不同方法,包括Mixins、高阶组件(HOC)、Render Props以及最新引入的Hooks。Mixins在ES6后逐渐被淘汰,HOC虽然解决了部分问题,但仍有命名冲突和理解难度。Render Props提供了一种替代方案,但可能引发不可预料的后果。最后,Hooks提供了更优雅的解决方案,允许在不编写类的情况下使用state,减少了组件间的嵌套和状态不确定性,提高了代码的可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们知道组件和自顶向下的单向数据流帮我们将大型 UI 组织成小的、独立的、可复用的部分。然而,由于逻辑是有状态的,不能提取到函数或其他组件,我们通常无法进一步分解复杂组件。

这些情况非常常见,包括动画、表单处理、异步请求数据等,以及我们希望从组件中完成的许多其他事情。 当我们试图单独使用组件来解决这些用例时,我们通常会得到:

  • 难以重构和测试的大型组件(我们已经制造了很多这种组件)
  • 不同组件和生命周期方法之间的重复逻辑(各种场景都很常见)
  • 发明了很多复杂的模式,比如 Mixins、 Render porp 和 高阶组件(HOC)

Mixins

主要应用在ES6普及之前,在还使用React.createClass时是非常常见的一种方式,例如:

var LogMixin = {
    log: function() {
        console.log('log');
    },
    componentDidMount: function() {
        console.log('in');
    },
    componentWillUnmount: function() {
        console.log('out');
    }
};

var User = React.createClass({
    mixins: [LogMixin],
    render: function() {
        return (<div>...</div>)
    }
});

var Goods = React.createClass({
    mixins: [LogMixin],
    render: function() {
        return (<div>...</div>)
    }
});

通过 Mixins,UserGoods 都实现了逻辑复用

  • ES6 class,规范不支持 mixins。
  • 不够直接,mixins 改变了 state,因此也就很难知道一些 state 是从哪里来的,尤其是当不止存在一个 mixins 时,可能还会相互依赖,相互耦合,及不利于代码维护。
  • 命名冲突,不同的 mixins 中的方法可能会相互冲突、相互覆盖。

HOC

高阶组件是参数为组件,返回值为新组件的函数。

因为Mixin带来的危害比他产生的价值还要大,所以React全面推荐使用高阶组件来替代它。

// 此函数接收一个组件...
function withSubscription(WrappedComponent, selectData) {

    // ...并返回另一个组件...
    return class extends React.Component {
        this.state = {
            data: selectData(DataSource, props)
        };

        componentDidMount() {
            // ...负责订阅相关的操作...
            DataSource.addChangeListener(this.handleChange);
        }

        this.handleChange = () => {
            this.setState({
                data: selectData(DataSource, this.props)
            });
        }

        render() {
            // ... 并使用新数据渲染被包装的组件!
            // 请注意,我们可能还会传递其他属性
            return <WrappedComponent data={this.state.data} {...this.props} />;
        }
    };
}

但是,是否解决了在使用 mixin 时遇到的问题?

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

而且引入了更多的规则:

  • 不能在 render 方法中使用 HOC
  • 务必复制静态方法
  • Refs 不会被传递
  • 理解难度上升,开发者需要“守规矩”(不修改传入组件的原型、透传 props 等)

高阶组件主要解决的问题是代码复用,但没有解决状态的不明确性以及命名冲突,很简单的一个例子就是封装input组件的时候,需要时刻注意value不被重置。

Render Props

由于HOC也存在一定的复杂性,社区又探索出一种新的模式:

在 React 组件之间使用一个值为函数的 prop 来共享代码

具体实现类似:

// 一个类型为函数的prop
class Say extends React.Component {
    static propTypes = {
        render: PropTypes.func.isRequired
    }
 // 状态
 state = { year: 2020 }

    render() {
        return (
            <div style={{ height: '100%' }}>
             {this.props.render(this.state) /* 与子组件共享状态 */}
            </div>
     )
    }
}

// 调用
<Say render={({ year }) => (
    // 传入一个可渲染的函数
    <div>hi! {year}.</div>
)}/>

虽然解决了 HOC 和 Mixins 中来源不清的 state、props以及命名冲突。但是很不直观,比较反直觉,而且引入了不必要的嵌套。

并且,当我们的使用者的组件为一个React.PureComponent的时候,由于浅比较 props 的时候总是false,所以会发生不可预料的后果,这就限制了使用者定义的组件。

Hooks

可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hooks将 React 哲学(显式数据流和组合)应用于组件内部,而不仅仅是组件之间。

与Render props 或高阶组件等模式不同,hook不会在组件树中引入不必要的嵌套。 也就没有mixins等的缺点。

假设我们设计一个监听当前窗口宽度的组件(例如,在一个可变视区上显示它的宽度)。

现在有几种方法可以编写这种代码,包括编写一个类,设置一些生命周期方法,或者想在组件之间重用它,甚至可以抽象一个 render prop 或一个高阶组件。但我认为这样会更好:

function MyResponsiveComponent() {
    const width = useWindowWidth(); // 使用自定义hook,获取窗口宽高
    return (
        <p>Window width is {width}</p>
 );
}

function useWindowWidth() {
    const [width, setWidth] = useState(window.innerWidth);
  const handleResize = () => setWidth(window.innerWidth);

    useEffect(() => {
        window.addEventListener('resize', handleResize);
        return () => {
            window.removeEventListener('resize', handleResize);
        };
    }, []);

    return width;
}

甚至,自定义的hook我们可以实现为这样:

function useWindowWidth() {
    const [width, setWidth] = useState(window.innerWidth);

    useEffect(() => {
        setWidth(window.innerWidth);
    });

    return width;
}

Hooks实现的伪代码

// by @jamiebuilds

let hooks = null;

export function useHook() {
    hooks.push(hookData);
}

// react内部渲染组件的方法:
function reactInternalRenderAComponentMethod(component) {
    hooks = [];
    component();
    let hooksForThisComponent = hooks;
    hooks = null;
}

由于 Hooks 在每次渲染时的顺序都是相同的,因此我们可以为每次调用提供正确的组件状态。

React 把 Hooks 的状态保持在 React 保存类状态的同一个地方。 React 有一个内部更新队列,它是任何状态的真实来源,不论如何定义组件。

由于功能逻辑等的拆离,会让业务组件变得更好维护,下图可以直观感受下:

由此,我们只需要实现较小粒度逻辑的hooks,然后在组件中组合它们就可以完成复杂的交互逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值