原文链接: medium.com/javascript-…

作者:Eric Elliott

为什么每一个 React 开发者都应该学习复合函数?

幻想一下,你正在构建一个 React 使用。你在这个使用的视图上有相当多的作业想要做。

  • 检查和更新用户的身份认证状况
  • 检查当前活跃的特性,以决定渲染哪些特性(需求继续交付【CD】)
  • 打印出每个页面的组件数量
  • 渲染一个规范的布局(像导航、边框等等)

像这样的作业一般被称为横切关注点,起先,你并不好这样想他们,你仅仅厌烦了将一堆模版代码复制粘贴进每个组件中去。比如下面这些代码。

const MyPage = ({ user = {}, signIn, features = [], log }) => {
  // Check and update user authentication status
  useEffect(() => {
    if (!user.isSignedIn) {
      signIn();
    }
  }, [user]);  // Log each page component mount
  useEffect(() => {
    log({
      type: 'page',
      name: 'MyPage',
      user: user.id,
    });
  }, []);  return <>
    {
      /* render the standard layout */
      user.isSignedIn ?
        <NavHeader>
          <NavBar />
          {
            features.includes('new-nav-feature')
            && <NewNavFeature />
          }
        </NavHeader>
          <div className="content">
            {/* our actual page content... */}
          </div>
        <Footer /> :
        <SignInComponent />
    }
  </>;
};

咱们能够经过将所有的函数别离抽象为 Provider 组件来摆脱上面那种令人讨厌的写法。然后咱们的页面看起来就像下面一样:

const MyPage = ({ user = {}, signIn, features = [], log }) => {
    return (  
    <>  
        <AuthStatusProvider>  
            <FeatureProvider>  
                <LogProvider>  
                    <StandardLayout>  
                        <div className="content">{/* our actual page content... */}</div>  
                    </StandardLayout>  
                </LogProvider>  
            </FeatureProvider>  
        </AuthStatusProvider>  
    </>  
    );  
};

不过咱们还是有一些问题,假如咱们的规范横切关注点发生了改变,咱们需求在每个页面上更改它们,并使所有页面保持同步。咱们还必须记住将 Provider 组件添加到每个页面。

高阶组件

一个更好的解决办法是用高阶组件去包裹咱们的页面组件。这是一个函数,他承受一个组件,然后返回一个新的组件。这个组件会渲染新的组件,可是会增加一些额定的功用。咱们能够用它来用咱们需求的Provider组件包裹咱们的页面

const MyPage = ({ user = {}, signIn, features = [], log }) => {
  return <>{/* our actual page content... */}</>;
};const MyPageWithProviders = withProviders(MyPage);

让咱们来看看 logger 用 HOC 实现会看起来像啥样子:

const withLogger = (WrappedComponent) => {
    return function LoggingProvider ({user, ...props}) {
        useEffect(() => {
            log({
                type: 'page',
                name: 'MyPage',
                user: user.id
            });
        }, []);
        return <WrappedComponent {...props} />
    }
}

数组

为了让咱们的Provider函数在一起作业的更好,咱们能够用函数组合将它们结合起来,放入一个单个的 HOC 里边。函数组合是一个结合两个或多个函数并生成一个新的函数的进程。他是一个很有力量的概念,能够被用来构建复杂的使用。

函数组合是将一个函数使用于另一个函数的返回值。在代数中,它由函数复合运算符表示

(f ∘ g)(x) = f(g(x))

Javascript 中,咱们能够写一个叫 compose 的函数,而且用它去组成更高阶的组件。

const compose = (...fns) => (x) => fns.reduceRight((y, f) => fn(y), x);
const withProviders = compose(
    withUser,
    withFeatures,
    withLogger,
    withLayout
)
export default withProviders

现在你能够在任何你需求的地方引进 withProviders 了。不过咱们还没做完呢。大多数的使用有很多不同的页面,而且不同的页面有时会有不同的需求。比如说,咱们有时不想要展现页脚(例如,在具有无限内容流的页面上)

函数柯里化

柯里化函数是一个一次承受多个参数的函数,经过返回一系列函数,每个函数承受下一个参数。

// Add two numbers, curried:
const add = (a) => (b) => a + b;// Now we can specialize the function to add 1 to any number:
const increment = add(1);

这是一个很小的例子,可是柯里化有助于函数组合,由于一个函数只能返回一个值。假如咱们想要自定义布局函数以获取额定的参数,最好的解决方案就是将它柯里化。

const withLayout = ({ showFooter = true }) =>
    (WrappedComponent) => {  
        return function LayoutProvider ({ features, ...props}) {  
            return (  
                <>  
                    <NavHeader>  
                        <NavBar />  
                            {  
                            features.includes('new-nav-feature')  
                            && <NewNavFeature />  
                            }  
                            </NavHeader>  
                            <div className="content">  
                            <WrappedComponent features={features} {...props} />  
                        </div>  
                    { showFooter && <Footer /> }  
                </>  
            );  
        };  
    };

可是咱们不能只柯里化布局函数,咱们还需求柯里化withProviders函数:

const withProviders = (options) =>
  compose(
    withUser,
    withFeatures,
    withLogger,
    withLayout(options)
  );

现在咱们就能够用 withPrivider 去包裹咱们想要使用Provider组件的任何页面组件,而且对每个页面的布局做定制化。

const MyPage = ({ user = {}, signIn, features = [], log }) => {
  return <>{/* our actual page content... */}</>;
};
const MyPageWithProviders = withProviders({
  showFooter: false
})(MyPage);