原文链接: medium.com/javascript-…
作者:Eric Elliott
幻想一下,你正在构建一个 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);