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

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

作者:Eric Elliott

想象一下,你正在构建一个 React 应用。你在这个应用的视图上有相当多的事情想要做。

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

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

js 复制代码
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 组件来摆脱上面那种令人讨厌的写法。然后我们的页面看起来就像下面一样:

html 复制代码
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组件包裹我们的页面

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

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

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

函数组合

为了让我们的Provider函数在一起工作的更好,我们可以用函数组合将它们结合起来,放入一个单个的 HOC 里面。函数组合是一个结合两个或多个函数并生成一个新的函数的过程。他是一个很有力量的概念,可以被用来构建复杂的应用。

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

scss 复制代码
(f ∘ g)(x) = f(g(x))

在 Javascript 中,我们可以写一个叫 compose 的函数,并且用它去组成更高阶的组件。

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

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

函数柯里化

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

js 复制代码
// 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);

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

js 复制代码
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函数:

js 复制代码
const withProviders = (options) =>
  compose(
    withUser,
    withFeatures,
    withLogger,
    withLayout(options)
  );

现在我们就可以用 withPrivider 去包裹我们想要应用Provider组件的任何页面组件,并且对每个页面的布局做定制化。

js 复制代码
const MyPage = ({ user = {}, signIn, features = [], log }) => {
  return <>{/* our actual page content... */}</>;
};
const MyPageWithProviders = withProviders({
  showFooter: false
})(MyPage);
相关推荐
程序猿进阶12 分钟前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
前端百草阁20 分钟前
【TS简单上手,快速入门教程】————适合零基础
javascript·typescript
彭世瑜21 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40422 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish22 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
zwjapple22 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five23 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序23 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫54124 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
酷酷的威朗普25 分钟前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5