Evolving Patterns in React - React 中的模式演化

目录

  1. 1. Conditional Render(条件渲染)
  2. 2. Passing Down Props(向下传递 Props)
  3. 3. Destructuring Props(解构 Props)
  4. 4. Provider Pattern(Provider 模式)
  5. 5. High Order Components(高阶组件)
  6. 6. Render Props

翻译自:Evolving Patterns in React

Let’s take a closer look at some of the patterns that are emerging in the React ecosystem. These patterns improve readability, code clarity, and push your code towards composition and reusability.

让我们仔细看看出现在 React 生态系统中的一些模式。这些模式可提高你的代码的可读性和清晰度,更加的可组合性和可复用性。

I started working with React roughly about 3 years ago. At that time, there were no established practices from which to learn in order to leverage its capabilities.

大约3年前我开始使用 React 工作,那个时候,还没有一个成熟的可以参考的模式。

It took about 2 years for the community to settle around a few ideas. We shifted from React.createClass to the ES6 class and pure functional components. We dropped mixins and we simplified our APIs.

社区花了大约2年的时间才解决一些想法。我们从 React.createClass 转移到 ES6 类和纯函数组件。我们放弃了mixin,并简化了 API

Now as the community is larger than ever, we’re starting to see a couple of nice patterns evolving.

现在,社区比以往更大,我们开始看到一些不错的模式 演变 出来。

In order to understand these patterns you need a basic understanding of the React concepts and its ecosystem. Please note, however, that I will not cover them in this article.

为了理解这些模式,你需要对 React 的概念及其生态系统有一个基本的了解。请注意,我不会在这篇文章中介绍它们。

So let’s begin!

Conditional Render(条件渲染)

I’ve seen the following scenario in a lot of projects.

我在很多项目都看到过以下场景。

When people think of React and JSX, they still think in terms of HTML and JavaScript.

当人们想到 ReactJSX 时,他们心里仍然把它们当做 HTMLJavaScript

So the natural step is to separate the conditional logic from the actual return code.

所以自然而然的,会把条件逻辑与实际的返回代码 分开

const condition = true;

const App = () => {
const innerContent = condition ? (
<div>
<h2>Show me</h2>
<p>Description</p>
</div>
) : null;

return (
<div>
<h1>This is always visible</h1>
{ innerContent }
</div>
);
};

This tends to get out of control, with multiple ternaries at the beginning of each render function. You constantly have to jump inside the function to understand when a certain element is rendered or not.

在每个 render 函数的开始处有多个三元运算符,这往往会失去控制。你必须不断地跳到函数内部来理解某个元素何时被渲染。

As an alternative, try the following pattern, where you benefit from the execution model of the language.

相反,可以尝试以下模式替代,从语言的执行模型中受益。

const condition = true;

const App = () => (
<div>
<h1>This is always visible</h1>
{
condition && (
<div>
<h2>Show me</h2>
<p>Description</p>
</div>
)
}
</div>
);

If condition is false, the second operand of the && operator is not evaluated. If it is true, the second operand —or the JSX we wish to render is returned.

如果 condition 为 false,则 && 运算符的第二个操作数不会被运算。如果它为 true,则返回第二个操作数或我们想要呈现的JSX。

This allows us to mix UI logic with the actual UI elements in a declarative way!

这使我们能够以 声明 的方式将 UI 逻辑与实际的 UI 元素 混合 在一起!

Treat JSX like it’s an integral part of your code! After all, it’s just JavaScript.

将 JSX 视为代码的一部分!毕竟,它就是 JavaScript。

Passing Down Props(向下传递 Props)

When your application grows, you have smaller components that act as containers for other components.

当你的应用程序增长时,会有更小的组件作为其他组件的容器。

As this happens, you need to pass down a good chunk of props through a component. The component doesn’t need them, but its children do.

这时,你需要通过组件传递大量 props。该组件不需要它们,但它的子组件可能需要。

A good way of bypassing this is to use props destructuring together with JSX spread, as you can see here:

一个好的绕过它方法就是对传递给 JSX 的属性使用 属性解构,如下:

const Details = ( { name, language } ) => (
<div>
<p>{ name } works with { language }</p>
</div>
);

const Layout = ( { title, ...props } ) => (
<div>
<h1>{ title }</h1>
<Details { ...props } />
</div>
);

const App = () => (
<Layout
title="I'm here to stay"
language="JavaScript"
name="Alex"
/>
);

So now, you can change the props needed for Details and be sure that those props are not referenced in multiple components.

所以现在,你可以改变 Details 所需要的 Props ,并确保那些 props 没有在其他组件中被多余的引用。

Destructuring Props(解构 Props)

An app changes over time, and so do your components. A component you wrote two years ago might be stateful, but now it can be transformed into a stateless one. The other way around also happens a lot of times!

随着时间的推移,你的应用程序和组件也会变化。你两年前写的一个组件可能是有状态的,但现在它可以转化为无状态组件。反之,也会发生!

Since we talked about props destructuring, here’s a good trick I use to make my life easier on the long run. You can destructure your props in a similar manner for both types of components, as you can see below:

之前,我们讨论了属性解构,从长远来看,这是一个让我的生活更轻松的好方法。你可以按照类似的方式为两种不同类型的组件解构属性,如下所示:

const Details = ( { name, language } ) => (
<div>
<p>{ name } works with { language }</p>
</div>
);

class Details extends React.Component {
render() {
const { name, language } = this.props;
return (
<div>
<p>{ name } works with { language }</p>
</div>
)
}
}

Notice that lines 2–4 and 11–13 are identical. Transforming components is much easier using this pattern. Also, you limit the usage of this inside the component.

注意第 2-4 行和第 11-13 行是 等价 的。使用这种模式可以更轻松地转换组件。此外,还可以限制组件内部 this 的使用。

Provider Pattern(Provider 模式)

We looked at an example where props need to be sent down through another component. But what if you have to send it down 15 components?

前面,我们看了一个例子,Props 需要通过另一个组件传递。但是如果你不得不将它传递给 15 个组件呢?

Enter React Context!

This is not necessarily the most recommended feature of React, but it gets the job done when needed.

查看 React Context!这不一定是 React 最推荐的功能,但它可以在需要时完成工作。

It was recently announced that the Context is getting a new API, which implements the provider pattern out of the box.

最近 React 宣布 Context 正在更新一个新的 API,它实现了开箱即用的 provider 模式

If you are using things like React Redux or Apollo, you might be familiar with the pattern.

如果你正在使用的 React ReduxApollo,你可能会非常熟悉这种模式。

Seeing how it works with today’s API will help you understand the new API as well. You can play around with the following sandbox.

我们来看看它如何与现有的 API 协同工作,这将有助于你理解新的 Context API。你可以在 sandbox 中运行它。

import React from 'react';
import { render } from 'react-dom';
import PropTypes from 'prop-types';

class MousePositionProvider extends React.Component {
constructor() {
super();
this.state = { };
this.onMouseMove = this.onMouseMove.bind( this );
}

getChildContext() {
return {
posX: this.state.posX,
posY: this.state.posY
};
}

componentDidMount() {
window.addEventListener( "mousemove", this.onMouseMove );
}

onMouseMove( e ) {
this.setState({ posX: e.clientX, posY: e.clientY });
}

render() {
return this.props.children
}
}

MousePositionProvider.childContextTypes = {
posX: PropTypes.number,
posY: PropTypes.number
};

class MousePositionConsumer extends React.Component {
render() {
return (
<div>Your position is ( {this.context.posX},{this.context.posY} )</div>
)
}
}

MousePositionConsumer.contextTypes = {
posX: PropTypes.number,
posY: PropTypes.number
};

const App = () => (
<MousePositionProvider>
<div>
<MousePositionConsumer />
<MousePositionConsumer />
</div>
</MousePositionProvider>
);

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

The top level component——called Provider——sets some values on the context. The child components——called Consumers——will grab those values from the context.

顶级组件(Provider),在 Context 中设置了一些值。子组件(Consumers),从 Context 中获取这些值。

The current context syntax is a bit strange, but the upcoming version is implementing this exact pattern.

目前的 Context 的语法有点奇怪,但即将推出的新版本正在按照这种模式实现。

High Order Components(高阶组件)

Let’s talk about reusability. Together with dropping the old React.createElement() factory, the React team also dropped the support for mixins. They were, at some point, the standard way of composing components through plain object composition.

我们来谈谈可重用性。像被丢弃的旧的 React.createElement() 工厂方法一样,React 团队也放弃了对 mixins 的支持。在某种程度上来说,复合组件的标准方式是通过简单对象的组合。

High Order Components—HOCs from now on—went out to fill the need for reusing behavior across multiple components.

高阶组件 - 现在开始的 HOC - 能够满足跨多个组件重用行为的需求。

A HOC is a function that takes an input component and returns an enhanced/modified version of that component. You will find HOCs under different names, but I like to think of them as decorators.

HOC 是一个接收输入组件并返回该组件的 增强或修改 版本的函数。你会发现 HOC 有不同的名称,但我喜欢把它们看作 装饰器

If you are using Redux, you will recognize that the connect function is a HOC—takes your component and adds a bunch of props to it.

如果你使用 Redux,你会发现 connect 函数就是一个 HOC –接收你的组件,并为它添加一堆 Props

Let’s implement a basic HOC that can add props to existing components.

我们来实现一个基本的 HOC,它可以为现有的组件添加额外的属性。

const withProps = ( newProps ) => ( WrappedComponent ) => {
const ModifiedComponent = ( ownProps ) => ( // the modified version of the component
<WrappedComponent { ...ownProps } { ...newProps } /> // original props + new props
);

return ModifiedComponent;
};

const Details = ( { name, title, language } ) => (
<div>
<h1>{ title }</h1>
<p>{ name } works with { language }</p>
</div>
);

const newProps = { name: "Alex" }; // this is added by the hoc
const ModifiedDetails = withProps( newProps )( Details ); // hoc is curried for readability

const App = () => (
<ModifiedDetails
title="I'm here to stay"
language="JavaScript"
/>
);

If you like functional programming, you will love working with high order components. Recompose is a great package that gives you all these nice utility HOCs like withProps, withContext, lifecycle, and so on.

如果你喜欢函数式编程,你将会喜欢使用高阶组件。Recompose 是一个很好的软件包,可以为你提供所有这些好用的HOC,例如withProps, withContext, lifecycle 等等。

Let’s have a look at a very useful example of reusing functionality.

让我们来看一个很有用的 功能复用 的例子。

function withAuthentication(WrappedComponent) {
const ModifiedComponent = (props) => {
if (!props.isAuthenticated) {
return <Redirect to="/login" />;
}

return (<WrappedComponent { ...props } />);
};

const mapStateToProps = (state) => ({
isAuthenticated: state.session.isAuthenticated
});

return connect(mapStateToProps)(ModifiedComponent);
}

You can use withAuthentication when you want to render sensitive content inside a route. That content will only be available to logged-in users.

当你想要在一个路由内展示敏感的内容时,你可以使用 withAuthentication ,该内容将只对登录的用户可用。

This is a cross-cutting concern of your application implemented in a single place and reusable across the entire app.

这是一个cross-cutting concern,实现一次,并可以整个应用程序中重用。

However, there is a downside to HOCs. Each HOC will introduce an additional React Component in your DOM/vDOM structure. This can lead to potential performance problems as your application scales.

但是,HOC 有一个缺点。每个 HOC 将在你的 DOM/vDOM 结构中引入一个额外的 React 组件。随着应用程序的扩展,这可能会导致潜在的性能问题。

Some additional problems with HOCs are summarized in this great article by Michael Jackson. He advocates replacing HOCs with the pattern we’ll be talking about next.

Michael Jackson这篇不错的文章中总结了 HOC 的一些其他问题,他主张用我们接下来要讨论的模式来取代 HOC。

Render Props

While it is true that render props and HOCs are interchangeable, I don’t favor one over another. Both patterns are used to improve reusability and code clarity.

尽管 render propsHOCs 是可以互换的,但我并不赞成使用一个替换另一个,这两种模式都用于提高代码可复用性和清晰度。

The idea is that you yield the control of your render function to another component that then passes you back the control through a function prop.

这个想法是,你将渲染函数的控制权转让给另一个组件,然后另一个组件通过函数的 prop 传回给你。

Some people prefer to use a dynamic prop for this, some just use **this.props.children**.

有些人只是使用 **this.props.children**,而有些人更喜欢使用动态的 prop

I know, it’s still very confusing, but let’s see a simple example.

这样听起来仍然很混乱,我们来看一个简单的例子:

class ScrollPosition extends React.Component {
constructor() {
super();
this.state = { position: 0 };
this.updatePosition = this.updatePosition.bind(this);
}

componentDidMount() {
window.addEventListener( "scroll", this.updatePosition );
}

updatePosition() {
this.setState( { position: window.pageYOffset } )
}

render() {
return this.props.children( this.state.position )
}
}

const App = () => (
<div>
<ScrollPosition>
{ ( position ) => (
<div>
<h1>Hello World</h1>
<p>You are at { position }</p>
</div>
) }
</ScrollPosition>
</div>
);

Here we are using children as the render prop. Inside the <ScrollPosition> component we will send a function which receives the position as a parameter.

我们在这里使用 children 作为渲染道具。在 <ScrollPosition> 组件中,我们将传递一个接收 position 作为参数的函数。

Render props can be used in situations where you need some reusable logic inside the component and you don’t want to wrap your component in a HOC.

渲染道具可用于在组件内部需要一些可重用逻辑,并且你不希望将组件包装在 HOC 中的情况。

React-Motion is one of the libraries that offer some great examples of using render props.

React-Motion是一个提供了很好的使用了 render props 例子的库。

Finally, let’s look at how we can integrate async flows with render props. Here’s a nice example of creating a reusable Fetch component.

最后,我们来看看我们如何将 异步 流程与 render props 进行整合。这是一个很好的创建可重用的 Fetch 组件的例子。

I’m sharing a sandbox link so you can play with it and see the results.

我分享一个 sandbox 链接,以便你可以使用它并查看运行结果。

点击查看可运行示例

import React from 'react';
import { render } from 'react-dom';

class Fetch extends React.Component {
constructor() {
super();
this.state = {
content: ""
}
}
componentDidMount() {
this.setState({ content: this.props.loading() })
fetch(this.props.url)
.then(res => res.json())
.then(
res => this.setState({ content: this.props.done(res) }),
res => this.setState({ content: this.props.error() })
)
}

render() {
return this.state.content;
}
}

const App = () => (
<Fetch
url="https://www.booknomads.com/api/v0/isbn/9789029538237"
loading={() => (
<div>Loading ... </div>
)}
done={(book) => (
<div>You asked for: { book.Authors[0].Name } - {book.Title}</div>
)}
error={() => (
<div>Error fetching content</div>
)}
/>
);

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

You can have multiple render props for the same component. With this pattern, you have endless possibilities of composing and reusing functionality.

你可以为同一个组件提供多个可渲染的 props。有了这种模式,你可以无限制地编写和重用功能。

What patterns do you use? Which of them would fit in this article? Drop me a message bellow or write your thoughts on Twitter.

你使用过什么样的模式?有哪一个适合这篇文章?给我发一封消息,或在 Twitter 上写下你的想法。

If you found this article useful, help me share it with the community!

知识共享许可协议 知识共享许可协议 知识共享许可协议 本网站原创内容(非转载文章)采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。