MarchYuanx

  • 首页

  • 归档

[译] React 16 生命周期函数:如何以及何时使用它们

发表于 2019-09-09 更新于 2019-10-12
  • 原文地址:React 16 Lifecycle Methods: How and When to Use Them
  • 原文作者:Scott Domes
  • 译文出自:掘金翻译计划
  • 本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO1/react-16-lifecycle-methods-how-and-when-to-use-them.md
  • 译者:MarchYuanx
  • 校对者:hanxiaosss, Zelda256

React 16 生命周期函数:如何以及何时使用它们

React 组件生命周期的修订版最新指南

自从我关于这个主题的第一篇文章以来,React 组件 API 发生了显著的变化。一些生命周期函数已被弃用,一些新的被引入。所以是时候进行更新了!

(看看我是如何抵制开 shouldArticleUpdate 的玩笑的?这些就是约束。)

由于这次生命周期 API 有点复杂,我将这些函数分为四个部分:挂载、更新、卸载和错误。

如果你对 React 还不太熟悉,我的文章在这提供了一个全面的介绍。

提示:

使用可复用的组件库更快地构建 React 应用程序。使用 Bit 共享你的组件,并使用它们来构建新的应用程序。试试看。

发现、尝试、使用集成 bit 的 React 组件

组件发现与协作 · Bit

问题

我们这个教程的示例应用程序很简单:一个由块组成的网格,每个块都有一个随机尺寸,排列成一个砖石布局(就像 Pinterest)。

每隔几秒钟,页面底部会加载一堆新的需要进行排列的块。

你可以查看最终的应用程序及其代码。

这不是一个复杂的例子,但这里有一个问题:我们将使用 bricks.js 库来对齐网格。

Brick.js 是一个很棒的工具,但它没有对与 React 集成进行优化。它更适合普通的 JavaScript 或 jQuery。

那我们为什么要用它?嗯,这是生命周期函数的一个常见用例:将非 React 工具集成到 React 范例中。

有时,实现功能最好的库不是最适配的库,生命周期函数有助于弥合这一差距。


在我们深入研究前的最后一点

如上所述,生命周期函数是最后的手段。

它们将被用于特殊情况,当处于其他回退时将无法工作,如重新排列组件或改变 state。

生命周期方法(constructor 除外)很难解释。它们会增加应用程序的复杂性。除非必须,否则不要使用它们。

不废话了,让我们来看看吧。

挂载

constructor

如果你的组件是类组件,第一个被调用的是组件构造函数。这不适用于函数组件。

你的构造函数可能如下所示:

1
2
3
4
5
6
7
8
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
}

调用构造函数时传入组件的 props。** 你必须调用 super 并传入 props。**

然后,你可以初始化 state,设置默认值。你甚至可以根据 props 设置 state:

1
2
3
4
5
6
7
8
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: props.initialCounterValue,
};
}
}

请注意,使用构造函数的方式是可选的,如果你的 Babel 设置支持类字段,则可以这样初始化 state:

1
2
3
4
5
class MyComponent extends Component {
state = {
counter: 0,
};
}

这种方法广受欢迎。 你仍然可以根据 props 设置 state:

1
2
3
4
5
class MyComponent extends Component {
state = {
counter: this.props.initialCounterValue,
};
}

但是,如果需要使用 ref,你可能仍需要构造函数。以下是我们的网格应用程序中的示例:

1
2
3
4
5
6
7
8
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
blocks: [],
};
this.grid = React.createRef();
}

我们需要构造函数调用 createRef 来创建对 grid 元素的引用,因此我们可以将它传递给 bricks.js。

你还可以使用构造函数进行函数绑定,这也是可选的。有关更多信息,请参阅此处:

React 绑定模式:处理 this 的 5 种方法

constructor 的最常见用例: 设置 state、创建引用和函数绑定。

getDerivedStateFromProps

挂载时,getDerivedStateFromProps 是渲染前调用的最后一个方法。你可以通过它根据初始的 props 设置 state。这是示例 Grid 组件的代码:

1
2
3
static getDerivedStateFromProps(props, state) {
return { blocks: createBlocks(props.numberOfBlocks) };
}

我们来看 numberOfBlocks prop,使用它来创建一组随机大小的块。然后我们返回想要的 state 对象。

以下是调用 console.log(this.state) 之后的内容:

1
2
console.log(this.state);
// -> {blocks: Array(20)}

请注意,我们可以将此代码放在 constructor 中。getDerivedStateFromProps 的优点是它更直观 —— 它只用于设置 state,而构造函数有多种用途。getDerivedStateFromProps 在挂载和更新之前都会被调用,我们稍后会看到。

getDerivedStateFromProps 的最常见用例(挂载期间):返回基于初始 props 的 state 对象。

render

渲染完成所有工作。它返回实际组件的 JSX。使用 React 时,大部分时间都将花费在这。

render 的最常见用例: 返回组件 JSX。

componentDidMount

在我们第一次渲染我们的组件之后,此方法被调用。

如果你需要加载数据,请在此处执行操作。不要尝试在 constructor、render 或是其他疯狂的地方加载数据。我会让 Tyler McGinnis 解释原因:

你无法确保在组件挂载之前 AJAX 请求不会被解析。如果确实如此,那就意味着你要在未挂载的组件上尝试执行 setState,这不仅不起作用,而且 React 会给你报一大堆错误。在 componentDidMount 中执行 AJAX 请求将保证这的确有一个组件需要更新。

你可以在他的回答里阅读更多。

componentDidMount 也是你可以做很多有趣事情的地方,那些在组件未加载时可没法做。以下是一些例子:

  • 绘制刚刚渲染的 <canvas> 元素
  • 在元素集合中初始化砖石网格布局(就是我们正在做的!)
  • 添加事件监听器

基本上,在这里你可以做所有没有 DOM 你不能做的设置,并获取所有你需要的数据。

以下我们的示例:

1
2
3
4
5
6
7
8
componentDidMount() {
this.bricks = initializeGrid(this.grid.current);
layoutInitialGrid(this.bricks)

this.interval = setInterval(() => {
this.addBlocks();
}, 2000);
}

我们使用 bricks.js 库(在 initializeGrid 函数中调用)创建并排列网格。

然后我们设置一个定时器,每两秒添加更多的块,模拟数据的加载。你可以设想这是一个 loadRecommendations 的调用或是现实世界中的某些东西。

componentDidMount 的最常见用例: 发送 AJAX 请求以加载组件的数据。

更新

getDerivedStateFromProps

是的,又是这个。现在,它更有用了。

如果你需要根据 prop 的改变更新 state,可以通过返回新 state 对象来执行此操作。

同样,不推荐基于 props 更改 state。这应该被视为最后的手段。问问自己 —— 我需要存储 state 吗?我不可以只从 props 本身获得正确的功能吗?

也就是说,存在一些边缘案例。以下是一些例子:

  • 当源更改时重置视频或音频元素
  • 使用服务器的更新刷新 UI 元素
  • 当内容改变时关闭手风琴元素

即使有上述情况,通常也有更好的方法。但是,当最坏的情况发生时,getDerivedStateFromProps 会帮你挽救回来。

使用我们的示例应用程序,假设我们的 Grid 组件的 numberOfBlocks prop 增加了。但是我们已经“加载”了比新数量更多的块。使用相同的值没有意义。所以我们这样做:

1
2
3
4
5
6
7
static getDerivedStateFromProps(props, state) {
if (state.blocks.length > 0) {
return {};
}

return { blocks: createBlocks(props.numberOfBlocks) };
}

如果我们当前 state 中的块数超过了新的 prop,我们根本不更新状态,返回一个空对象。

(关于 static 函数的最后一点,比如 getDerivedStateFromProps:你没有通过 this 访问组件的权限。例如,我们无法访问的网格的引用。)

getDerivedStateFromProps 的最常见用例: 当 props 本身不足时,根据情况更新 state。

shouldComponentUpdate

当我们有新的 props 时。典型的 React 法则是,当一个组件接收到新的 props 或者新的 state 时,它应该更新。

但我们的组件有点担忧,得先征得许可。

这是我们得到的 —— shouldComponentUpdate 函数,调用时以 nextProps 作为第一个参数,nextState 是第二个。

shouldComponentUpdate应该总是返回一个布尔值 —— 问题的答案,“我应该重新渲染吗?”是的,小组件,你应该。它返回的默认值始终是 true。

但如果你担心浪费渲染资源和其它无意义的事 —— shouldComponentUpdate 是一个提高性能的好地方。

我写了一篇关于用这种方式使用 shouldComponentUpdate 的文章 —— 请看:

如何对 React 组件进行基准测试:快速简要指南

在文章中,我们谈论有一个包含许多部分的表格。问题是当表重新渲染时,每个部分也都会重新渲染,从而减慢了速度。

shouldComponentUpdate 让我们能够说:组件只在你关心的 props 改变时才更新。

但请牢记,如果你设置了并忘记了它会导致重大问题,因为你的 React 组件将无法正常更新。所以要谨慎使用。

在我们的网格应用程序中,我们之前已经确定了有时我们将忽略 this.props.numberOfBlocks 的新值。默认行为表示我们的组件仍然会重新渲染,因为它收到了新的 props。这太浪费了。

1
2
3
4
shouldComponentUpdate(nextProps, nextState) {
// Only update if bricks change
return nextState.blocks.length > this.state.blocks.length;
}

现在我们可以说:只有当 state 中的块数改变时,组件才应该更新。

shouldComponentUpdate 的最常见用例: 精确控制组件的重新渲染。

render

和之前一样!

getSnapshotBeforeUpdate

这个函数是一个有趣的新增功能。

请注意,它是在 render 与更新组件被实际传播到 DOM 之间调用的。在你的组件中,它作为最后一次看到之前的 props 和 state 的机会存在。

为什么?好吧,在调用 render 和显示更改之间可能会有一段延迟。如果你需要在整合最新 render 调用的结果时知道 DOM 是什么**,这里就是你可以找到答案的地方。

这是一个例子。假设我们的团队负责人决定,如果用户在加载新块时位于网格底部,则应将其向下滚动到屏幕的新底部。

换句话说:当网格扩展时,如果它们位于底部,请让它们继续在底部。

1
2
3
4
5
6
7
8
9
10
11
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevState.blocks.length < this.state.blocks.length) {
const grid = this.grid.current;
const isAtBottomOfGrid =
window.innerHeight + window.pageYOffset === grid.scrollHeight;

return { isAtBottomOfGrid };
}

return null;
}

这就是说:如果用户滚动到底部,则返回如下对象:{isAtBottomOfGrid:true}。如果没有,则返回 null。

你应该返回 null 或从 getSnapshotBeforeUpdate 获取的值。

为什么?我们马上就能看到。

getSnapshotBeforeUpdate 的最常见用例: 查看当前 DOM 的一些属性,并将值传给 componentDidUpdate。

componentDidUpdate

现在,我们的更改已经提交给 DOM。

在 componentDidUpdate 中,我们可以访问三个东西:之前的 props、之前的 state 以及我们从 getSnapshotBeforeUpdate 返回的任何值。

完成上面的例子:

1
2
3
4
5
6
7
8
9
10
componentDidUpdate(prevProps, prevState, snapshot) {
this.bricks.pack();

if (snapshot.isAtBottomOfGrid) {
window.scrollTo({
top: this.grid.current.scrollHeight,
behavior: 'smooth',
});
}
}

首先,我们使用 Bricks.js 的 pack 函数重新布局网格。

然后,如果我们的快照显示用户原先就位于网格的底部,我们将它们向下滚动到新块的底部。

componentDidUpdate 的最常见用例: 响应(哈哈!)DOM 的已提交更改。

卸载

componentWillUnmount

它快要结束了。

你的组件将会消失。也许是永远。这很令人悲伤。

在此之前,它会询问你是否有任何最后一刻前的请求。

你可以在此处取消任何向外的网络请求,或删除与该组件关联的所有事件监听器。

总的来说,清除所涉及的组件的每一件事 —— 当它消失时,它应该完全消失。

在我们的例子中,我们有一个在 componentDidMount 中调用的 setInterval 需要清理。

1
2
3
componentWillUnmount() {
clearInterval(this.interval);
}

componentWillUnmount 的最常见用例: 清理组件中所有的剩余碎片。

错误

getDerivedStateFromError

有些东西出问题咯。

不是在你的组件本身,而是在它的某个子孙组件。

我们想要将错误显示在屏幕上。最简单的方法是使用一个像 this.state.hasError 这样的值,此时该值将转换为 true。

1
2
3
static getDerivedStateFromError(error) {
return { hasError: true };
}

请注意,你必须返回更新的 state 对象。不要将此方法作用于任何其他操作。而是使用下面的 componentDidCatch。

getDerivedStateFromError的最常见用例: 更新 state 以显示错误在屏幕上。

componentDidCatch

与上面非常相似,因为它在子组件中发生错误时被触发。

区别在于不是为了响应错误而更新 state,我们现在可以执行任何其他操作,例如记录错误。

1
2
3
componentDidCatch(error, info) {
sendErrorLog(error, info);
}

error 是实际的错误消息(未定义的变量之类),info 是堆栈跟踪(In Component, in div, etc)。

请注意,componentDidCatch 仅适用于渲染/生命周期函数中的错误。如果你的应用程序在点击事件中抛出错误,它不会被捕获。

你通常只在特殊的错误边界组件中使用 componentDidCatch。这些组件封装子组件的唯一目的是捕获并记录错误。

例如,此错误边界将捕获错误并呈现“Oops!”消息而不是子组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ErrorBoundary extends Component {
state = { errorMessage: null };

static getDerivedStateFromError(error) {
return { errorMessage: error.message };
}

componentDidCatch(error, info) {
console.log(error, info);
}

render() {
if (this.state.errorMessage) {
return <h1>Oops! {this.state.errorMessage}</h1>;
}

return this.props.children;
}
}

componentDidCatch 的最常见用例: 捕获并记录错误。

结论

就是这样!这些都是生命周期函数,任你使用。

你可以查看示例程序的代码和最终产品。

感谢阅读!请随意在下面发表评论并提出任何问题,欢迎交流!


延伸阅读

  • 5 个加快 React 开发的工具
  • 了解 React 渲染 props 和高阶组件
  • 在 2019 年你应该知道的 11 个 React UI 组件库

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。

[译] 通过阅读源码提高你的 JavaScript 水平

发表于 2019-07-30 更新于 2019-10-12
  • 原文地址:Improve Your JavaScript Knowledge By Reading Source Code
  • 原文作者:Carl Mungazi
  • 译文出自:掘金翻译计划
  • 译者:MarchYuanx
  • 校对者:imononoke, Baddyo

通过阅读源码提高你的 JavaScript 水平

快速摘要:当你还处于编程生涯的初期阶段时,深入研究开源库和框架的源代码可能是一项艰巨的任务。在本文中,Carl Mungazi 分享了他如何克服恐惧,并开始用源码来提高他的知识水平和专业技能。他还使用了 Redux 来演示他如何解构一个代码库。

你还记得你第一次深入研究你常用的库或框架的源码时的情景吗?对我来说,这一刻发生在三年前我作为前端开发者的第一份工作中。

当时我们刚刚完成了用于创建网络学习课程的内部遗留框架的重构。在重构开始时,我们花时间研究了许多不同的解决方案,包括 Mithril、Inferno、Angular、React、Aurelia、Vue 和 Polymer。那时我仅仅只是个小萌新(我刚从新闻工作转向 web 开发),我记得我对每个框架的复杂性感到恐惧,不理解它们是如何工作的。

随着对我们所选择的 Mithril 框架研究的深入,我对它的理解也逐渐加深了。从那以后,我花了很多时间深入钻研那些在工作或个人项目中日常使用的库的内部结构,这显著地提升了我对 JavaScript —— 以及通用编程思想 —— 的了解。在这篇文章中,我将分享一些方法给你,你可以使用自己喜欢的库或框架,并将其作为学习工具。

Mithril 中 hyperscript 函数的源码

我要介绍的第一个源码阅读示例是 Mithril 的 hyperscript 函数。(高清预览)

阅读源码的好处

阅读源代码的一个主要好处是可以学到很多东西。在我第一次读 Mithril 代码库时,我对虚拟 DOM 的概念还很模糊。当我读完后,我了解到虚拟 DOM 是一种技术,它创建一个对象树,用于描述用户界面的外观。然后使用 DOM APIs(如 document.createElement)将对象树转换为 DOM 元素。通过创建描述用户界面的更新状态的新对象树,然后将其与旧对象树进行比较来执行更新。

我在各种文章和教程中已经阅读了所有这些内容,虽然这很有帮助,但对我来说,能够在我们提供的应用程序的环境中观察到它工作是非常有启发性的。它还教会我在比较不同框架时应该考虑哪些因素。例如,我现在知道要考虑这样的问题,“每个框架执行更新的方式如何影响性能和用户体验?”,而不是只看框架在 GitHub 上 star 的数量。

另一个好处是你对优秀的程序架构的理解和鉴赏能力提升了。虽然大多数开源项目的存储库通常遵循相同的结构,但每个项目都包含差异。Mithril 的结构非常简单,如果你熟悉它的 API,你可以根据文件夹名称推测出其中的代码的功能,如 render、router 和 request。另一方面,React 的结构反映了它的新架构。维护人员将负责 UI 更新的模块(react concerner)与负责呈现 DOM 元素的模块(react dom)分开。

这样做的好处之一是,开发人员现在更容易通过挂进 react-reconciler 包来编写自己的自定义渲染器。我最近研究过的模块打包工具 Parcel 也有像 React 这样的 packages 文件夹。主模块名为 parcel-bundler,它包含负责创建包、启动热模块服务器和命令行工具的代码。

JavaScript 语言规范中解释 Object.prototype.toString 原理的章节

不久之后,你所阅读的源码将引导你找到 JavaScript 规范。(高清预览)

另一个好处 —— 令我感到惊讶的是 —— 你可以更轻松地阅读定义语言如何工作的官方 JavaScript 规范。我第一次阅读规范是在研究 throw Error 与 throw new Error(剧透警告 —— 二者没有区别)之间的区别时。我研究这个问题是因为我注意到 Mithril 在其 m 函数的实现中使用了 throw Error,我想知道这种用法是否比使用 throw new Error 更好。从那以后,我还了解了逻辑运算符 && 和 || 不一定返回布尔值,找到了控制 == 等于运算符如何强制转换值的规则和 Object.prototype.toString.call({}) 返回 '[object Object]' 的原因。

阅读源码的技巧

有很多方法可以处理源码。我发现最简单的方法是从你选择的库中选择一个方法,并记录当你调用它时会发生什么。不要每一个步骤都记录,而是尝试理解它的整体流程和结构。

我最近用这个方法阅读了 ReactDOM.render 的源码,因此学到了很多关于 React Fiber 及其实现背后的一些原因。谢天谢地,由于 React 是一个流行的框架,在同样的问题上,我找到了很多其他开发者撰写的文章,这让我的学习进程快了许多。

这次深入研究还让我明白了合作调度的概念、window.requestIdleCallback 方法和一个链接列表的实际示例(React 通过将更新放入一个队列来处理它们,这个队列是一个按优先级排列的链接列表)。在研究过程中,建议使用库创建非常基本的应用程序。这使得调试更容易,因为你不必处理由其他库引起的堆栈跟踪。

如果我不打算进行深入研究,我会打开正在开发的项目中的 /node_modules 文件夹,或者到 GitHub 仓库中去查看源码。这通常发生在我遇到一个 bug 或有趣的特性时。在 GitHub 上阅读代码时,请确保你阅读的是最新版本。你可以通过单击用于更改分支的按钮并选择“tags”来查看具有最新版本标记的提交中的代码。库和框架永远在进行更改,因此你不会想了解可能在下一版本中删除的内容。

还有另一种不太复杂的阅读源码的方法,我喜欢称之为“粗略一瞥”。在我开始阅读代码的早期,我安装了 express.js,打开了它的 /node_modules 文件夹并浏览了它的依赖项。如果 README 没有给我一个满意的解释,我就会阅读源码。这样做让我得到了这些有趣的发现:

  • Express 依赖于两个模块,两个模块都合并对象,但以非常不同的方式进行合并。merge-descriptors 只添加直接在源对象上直接找到的属性,它还合并了不可枚举的属性,而 utils-merge 只迭代对象的可枚举属性以及在其原型链中找到的属性。merge-descriptors 使用 Object.getOwnPropertyNames() 和 Object.getOwnPropertyDescriptor() 而 utils-merge 使用 for..in;
  • setprototypeof 模块提供了一种设置实例化对象原型的跨平台方式;
  • escape-html 是一个有 78 行代码的模块,用于转义一系列内容,可以在 HTML 内容中进行插值。

虽然这些发现不可能立即有用,但是对库或框架所使用的依赖关系有一个大致的了解是有用的。

在调试前端代码时,浏览器的调试工具是你最好的朋友。除此之外,它们允许你随时停止程序并检查其状态,跳过函数的执行或进入或退出程序。有时这不能立即生效,因为代码已经压缩。我倾向于将它解压并将解压的代码复制到 /node_modules 文件夹中的对应文件中。

ReactDOM.render 函数的源码

像处理任何其他应用程序一样处理调试。形成一个假设,然后测试它。(高清预览)

研究案例:Redux 的 Connect 函数

React-Redux 是一个用于管理 React 应用程序状态的库。在处理这些流行的库时,我首先搜索有关其实现的文章。在这个案例研究中,我找到了这篇文章。这是阅读源码的另一个好处。研究阶段通常会引导你阅读这样的信息性文章,这些文章会提高你的思考与理解。

connect 是一个将 React 组件连接到应用程序的 Redux 存储的 React-Redux 函数。怎么连?好的,根据文档,它执行以下操作:

“…返回一个新的连接的组件类,它包装您传入的组件。”

看完之后,我会问下列问题:

  • 我是否知道哪些模式或概念,其函数能够接受一个输入并将输入封装、加上附加功能再返回输出?
  • 如果我知道这样的模式,我如何根据文档中给出的解释来实现它?

通常,下一步是创建一个使用 connect 的非常基础的示例应用程序。但是,在这种情况下,我选择使用我们在 limejump 上构建的新的 React 应用程序,因为我希望在最终要进入生产环境的应用程序的上下文环境中理解 connect。

我关注的组件看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
class MarketContainer extends Component {
// 简洁起见,省略代码(code omitted for brevity)
}

const mapDispatchToProps = dispatch => {
return {
updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today))
}
}

export default connect(null, mapDispatchToProps)(MarketContainer);

它是一个容器组件,包裹着四个较小的连接的组件。在导出 connect 方法的文件中,你首先看到的是这个注释:connect is a facade over connectAdvanced。没走多远,我们就有了第一个学习的时刻:一个观察 facade 设计模式的机会。在文件末尾,我们看到 connect 导出了对名为 createConnect 的函数的调用。它的参数是一组默认值,这些默认值被这样解构:

1
2
3
4
5
6
7
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {})

同样,我们遇到了另一个学习时刻:导出调用函数和解构默认函数参数。解构部分是一个学习时刻,因为它的代码编写如下:

1
2
3
4
5
6
7
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
})

它会导致这个错误 Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.。这是因为函数没有可供回调的默认参数。

注意:有关这方面的更多信息,您可以阅读 David Walsh 的文章。根据你对语言的了解,一些学习时刻可能看起来微不足道,因此最好将注意力放在您以前从未见过的事情上,或需要了解更多信息的事情上。

createConnect 在其函数内部并不执行任何操作。它只是返回一个名为 connect 的函数,也就是我在这里用到的:

1
export default connect(null, mapDispatchToProps)(MarketContainer)

它需要四个参数,都是可选的,前三个参数都通过 match 函数来帮助根据参数是否存在以及它们的值类型来定义它们的行为。现在,因为提供给 match 的第二个参数是导入 connect 的三个函数之一,我必须决定要遵循哪个线程。

如果那些参数是函数,代理函数被用来将第一个参数包装为 connect,这是也一个学习的时刻。isPlainObject 用于检查普通对象或 warning 模块,它揭示了如何将调试器设置为中断所有异常。在匹配函数之后,我们来看 connectHOC,这个函数接受我们的 React 组件并将它连接到 Redux。它是另一个函数调用,返回 wrapWithConnect,该函数实际处理将组件连接到存储的操作。

看看 connectHOC 的实现,我可以理解为什么它需要 connect 来隐藏它的实现细节。它是 React-Redux 的核心,包含不需要通过 connect 展现的逻辑。尽管我原本打算在这个地方结束对它的深度探讨,我也会继续,这将是查阅之前发现的参考资料的最佳时机,因为它包含对代码库的非常详细的解释。

总结

阅读源码起初很困难,但与任何事情一样,随着时间的推移变得更容易。我们的目标不是理解一切,而是要获得不同的视角和新知识。关键是要对整个过程进行深思熟虑,并对所有事情充满好奇。

例如,我发现 isPlainObject 函数很有趣,因为它使用 if (typeof obj !== 'object' || obj === null) return false 以确保给定的参数是普通对象。 当我第一次阅读它的实现时,我想知道为什么它没有使用 Object.prototype.toString.call(opts) !== '[object Object]' ,这样能用更少的代码且区分对象和对象子类型,如 Date 对象。但是,读完下一行我发现,在极小概率情况下,例如开发者使用 connect 时返回了 Date 对象,这将由Object.getPrototypeOf(obj) === null 检查处理。

isPlainObject 中另一个吸引人的地方是这段代码:

1
2
3
while (Object.getPrototypeOf(baseProto) !== null) {
baseProto = Object.getPrototypeOf(baseProto)
}

有些谷歌搜索结果指向这个 StackOverflow 问答和这个在 GitHub 仓库中的 Redux issue,解释该代码如何处理诸如检查源自 iFrame 的对象这类情况。

其它的阅读源码的参考链接

  • “How To Reverse Engineer Frameworks,” Max Koretskyi, Medium
  • “How To Read Code,” Aria Stewart, GitHub

[译] CSS 思维模式

发表于 2019-07-15 更新于 2019-07-30
  • 原文地址:The CSS Mindset
  • 原文作者:Max Böck
  • 译文出自:掘金翻译计划
  • 译者:MarchYuanx
  • 校对者:solerji, Chorer

CSS 思维模式

啊,是的,CSS。它几乎每个星期都是网络上热议的话题。它太难了。它太简单了。它无法预测。它过时了。此处应该有一张 Peter Griffin 折腾百叶窗的恶搞图。

我不知道为什么 CSS 会在开发者中激发出如此多的不同情绪,但我有一种直觉,为什么有时候它看起来不合逻辑或令人沮丧:你需要一种特定的思维模式才能写出好的 CSS。

现在,您可能需要一个编码的思维模式,但 CSS 的声明性使其特别难以掌握,尤其是当您用“传统”编程语言的思维来思考它的时候。

其他编程语言通常在受控的环境中工作,例如服务器。它们希望某些条件始终为真,从而作为程序应该如何执行的具体指令。

另一方面,CSS 在一个永远无法完全可控的地方工作,所以默认情况下它必须是灵活的。它不仅仅用于“编写外观”,还用于将设计转换为一组传达其背后意图的规则。留出足够的空间,浏览器将为您完成繁重的工作。

对于大多数专业编写 CSS 的人来说,他的思维模式在一段时间后自然而然地形成了。在事情终于有成效的时候,许多开发者都有那样一个“啊哈!”的时刻。这不仅仅是了解所有技术细节,更多的是关于语言背后的思想的一种感觉。

我试着在这里列出一些思维模式。

全都是矩形

这看起来很明显,因为盒子模型可能是人们学习 CSS 的时候接触到的第一个东西。但是将每个 DOM 元素描绘成一个盒子对于理解事物布局方式至关重要。它是内联的还是块级的?它是弹性的吗?它将如何在不同的环境中拉伸/收缩/包裹?

打开你的开发者工具并将鼠标悬停在元素上,观察它们绘制的盒子。或使用像 outline: 2px dotted hotpink 这样的显式样式来显示其隐藏的边框。

级联是你的朋友

级联 —— 一个可怕的概念,我懂。在镜子前说三次“级联”,在某个地方,一些不相关的样式会失效。

虽然有合理的理由避免级联,但这并不意味着它的一切都是不好的。事实上,如果使用得当,它可以让您的生活更轻松。

重要的是要知道哪些样式属于全局作用域,哪些样式更适合于组件。它还有助于了解传递的默认值,避免声明不必要的样式规则。

尽可能必要,尽可能少

旨在编写实现设计所需的最少量的代码。较少的属性意味着更少的继承、更少的限制和更少的覆盖带来的麻烦。想想你的选择器应该做什么,然后尝试就那样表达它。在已经是块级别的元素上声明 width: 100% 是没有意义的。如果您不需要新的堆叠上下文,则无需设置 position: relative。

避免不必要的样式,你就避免了意外后果。

简写有很大的影响

一些 CSS 属性可以用“简写”方式书写,这使得一起声明一组相关属性成为可能。虽然这很方便,请注意,使用简写还将为未显式设置的每个属性声明默认值。写上 background: white; 将有效地导致所有这些属性被设置:

1
2
3
4
5
6
7
8
background-color: white;
background-image: none;
background-position: 0% 0%;
background-size: auto auto;
background-repeat: repeat;
background-origin: padding-box;
background-clip: border-box;
background-attachment: scroll;

样式最好是明确的。 如果要更改背景颜色,请使用 background-color。

永远要灵活

CSS 处理大量未知的变量:屏幕大小、动态内容、设备功能 —— 这个列表还在继续。如果你的样式过于狭隘或受限,那么这些因素中的某一个很可能会让你栽跟头。这就是为什么写好 CSS 的一个关键方面就是接受它的灵活性

你的目标是编写一套足够全面的指令来描述你想要实现的页面,但足够灵活,让浏览器自己理解清楚怎么做。这就是为什么通常最好避免 “神奇数字”。

神奇数字是随机的固定值。比如:

1
.thing {    width: 218px; /* why? */}

每当你自己在开发工具中点击箭头键并调整一个像素值使之适合的时候 —— 都可能会有一个神奇数字。它们很少能解决 CSS 问题,因为它们将样式限制在特定的使用案例中。如果约束发生变化,那么该数字将会失效。

相反,想想在那种情况下你真正想要实现什么。对齐?宽高比?分配等量的空间?所有这些都有灵活的解决方案。在大多数情况下,最好为目的定义一个规则,而不是采用硬编码的计算方案。

语境是关键

对于许多布局概念,必须了解元素与其容器之间的关系。大多数组件是父节点和子节点的集合。应用于父级的样式会影响子孙级,这可能会使它们忽略其他规则。弹性盒子,栅格布局和 position: absolute 是此类错误的常见来源。

当疑惑某个特定元素的表现与您期望的不同时,请查看它所在的上下文。可能是它祖先级的某些因素影响了它。

内容会改变

始终要注意,您所看到的只是在大范围中的一种 UI 状态。不要在屏幕上设置样式,而是尝试构建组件的“蓝图”。然后确保不论你把它放在什么场景,都不会使你的样式失效。

字符串可能比预期长或包含特殊字符,图像可能缺失或具有奇怪的尺寸。显示的样子可能非常窄或非常宽。这些都是您需要预测的状态。

设计师和开发者犯的第一个错误就是假设事情总是像它们在静态模型中那样。我可以向你保证,它们不会。

发现模式并复用它们

当您打算将设计模型实现为代码时,首先盘点出所包含的不同模式通常很有帮助。分析每个屏幕的场景,注意任何出现一次以上的概念。它可能是一些小的东西,比如排版样式,或者大的东西,比如某种布局模式。

什么可以抽象?什么是特有的?将设计中的各个部分视为独立的东西使它们更易于理解,并有助于划分组件之间的界限。

使用一致的命名

总的来说,相当多的一部分程序有不错的命名。在 CSS 中,它有助于遵守约定。像 BEM 或 SMACSS 这样的命名方案非常有用;但即使你不使用它们,也要坚持使用一致的词汇。


👉 免责声明
所有这些对我来说都是很重要的,但是基于你的个人经历,什么最重要可能是不同的。你有没有你的“啊哈”时刻让你更好地理解 CSS?告诉我!

延伸阅读

  • How to learn CSS by Rachel Andrews
  • The Secret Weapon to learning CSS by Robin Rendle
  • CSS doesn’t suck by Andy Bell

个人信息

发表于 2019-07-01 更新于 2019-09-09

学海无涯 冲冲冲 (ง •_•)ง

个人信息

  • 软件工程 / 本科 / 2019届
  • 男 / 1998

联系方式

  • 微信: yuans666666
  • 邮箱: 592302815@qq.com
  • Github: MarchYuanx

个人技能

  • 前端技能: HTML / CSS / JavaScript (es6) / ⼩程序
  • 前端框架: React (hook) / Redux / Electron
  • 前端工具: Webpack / Babel / TypeScript / Less
  • 数据库: MySQL / SqlServer / Oracle
  • 后端技能: Node / Java
  • 版本管理: Git
  • 服务器: nginx
  • 英语: CE6

个人项目

网易云课堂小程序云开发

技术栈 :小程序云开发 vant easymock

1. 微信小程序,负责前端页面开发、数据库设计与数据交互; 
2. 使用wxml+wxss+js+vant实现小程序静态页面的基本布局及数据渲染; 
3. 使用微信提供的云开发数据库进行文档型数据库设计、并使用云函数进行数据交互; 
4. 使用微信小程序api+js+ajax实现了商品展示、商品详情、购物车、订单中心等购物功能。

项目地址: 网易云课堂小程序 Github 掘金

单车后台管理系统

技术栈 :React Antd Axios Webpack Less Echarts easymock

1. React SPA 应用,负责前端页面开发、数据请求与渲染; 
2. 主要使用了 React、Redux 进行组件式开发,UI 选择了 Antd,并使用less 预编译处理器进行样式开发; 
3. 使用百度地图api实现地图的展示与路径的绘制; 
4. 使用Echarts 实现数据可视化的处理; 
5. 使用 Axios 进行数据请求,并对 ajax、jsonp 等请求进行二次封装。

项目地址: 单车后台管理系统 Github

其他

  • 参与掘金翻译计划
  • 熟悉前端工程化
  • 对前后端联合开发的技术原理有全面认识
  • 对DNS/HTTP和相关的其他底层网络协议有比较全面的了解

MarchYuanx

(゜-゜)つ🍺 干杯~
4 日志
3 标签
© 2019 MarchYuanx
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Muse v7.1.2