[qns] sync: sync system design question (#1435)
This commit is contained in:
parent
ba1faf3600
commit
f51e9c1bc0
|
|
@ -1,84 +0,0 @@
|
|||
I'm authoring system design questions for front end engineers. The system design questions are mainly for client applications and focus on product, user interface, user experience and less about the back end and distributed systems.
|
||||
|
||||
Requirements exploration involves understanding the problem thoroughly and determine the scope by asking a number of clarifying questions.
|
||||
|
||||
Given the following applications and example questions within code blocks, generate a suitable set of clarifying questions candidates should be asking for <QUESTION>. The title of the question is before the "===".
|
||||
|
||||
```
|
||||
News Feed
|
||||
===
|
||||
|
||||
### What are the core features to be supported?
|
||||
|
||||
- Browse news feed containing posts by the user and their friends.
|
||||
- Liking and reacting to feed posts.
|
||||
- Creating and publishing new posts.
|
||||
|
||||
Commenting and sharing will be discussed further down below but is not included in the core scope.
|
||||
|
||||
### What kind of posts are supported?
|
||||
|
||||
Primarily text and image-based posts. If time permits we can discuss more types of posts.
|
||||
|
||||
### What pagination UX should be used for the feed?
|
||||
|
||||
Infinite scrolling, meaning more posts will be added when the user reaches the end of their feed.
|
||||
|
||||
### Will the application be used on mobile devices?
|
||||
|
||||
Not a priority, but a good mobile experience would be nice.
|
||||
```
|
||||
|
||||
```
|
||||
Chat Application
|
||||
===
|
||||
|
||||
### What are the core functionalities needed?
|
||||
|
||||
- Sending a message to a user.
|
||||
- Receiving messages from a user.
|
||||
- See one's chat history with a user.
|
||||
|
||||
### Is the message receiving real-time?
|
||||
|
||||
Yes, users should receive messages in real-time, as fast as possible without having to refresh the page.
|
||||
|
||||
### What kind of message formats should be supported?
|
||||
|
||||
Let's support formats text which can contain emojis. We can discuss supporting images if there's time.
|
||||
|
||||
### Does the application need to work offline?
|
||||
|
||||
Yes, where possible. Outgoing messages should be stored and sent out when the application goes online and users should still be allowed to browse messages even if they are offline.
|
||||
|
||||
### Are there group conversations?
|
||||
|
||||
We can assume it's a 1:1 messaging service.
|
||||
```
|
||||
|
||||
```
|
||||
Travel booking (e.g. Airbnb)
|
||||
===
|
||||
|
||||
### What are the core features to be supported?
|
||||
|
||||
- Search and browse accommodation listings.
|
||||
- Viewing accommodation details such as price, location, photos, and amenities.
|
||||
- Make reservation for accommodations.
|
||||
|
||||
### What does the user demographics look like?
|
||||
|
||||
International users of a wide age range: US, Asia, Europe, etc.
|
||||
|
||||
### What are the non-functional requirements?
|
||||
|
||||
Each page should load under 2 seconds. Interactions with page elements should respond quickly.
|
||||
|
||||
### What devices will the website be used on?
|
||||
|
||||
All possible devices: laptop, tablets, mobile, etc.
|
||||
|
||||
### Do users have to be signed in?
|
||||
|
||||
Anyone can search for listings and browse details but users need to be logged in to make a booking.
|
||||
```
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
"f533e100",
|
||||
"291d8871",
|
||||
"9707b6ec",
|
||||
"100177d9",
|
||||
"43c1fed2",
|
||||
"a5940b33",
|
||||
"4e94cf49",
|
||||
"7aa49345",
|
||||
|
|
@ -161,7 +161,7 @@
|
|||
"f533e100",
|
||||
"291d8871",
|
||||
"9707b6ec",
|
||||
"100177d9",
|
||||
"43c1fed2",
|
||||
"a5940b33",
|
||||
"4e94cf49",
|
||||
"7aa49345",
|
||||
|
|
|
|||
|
|
@ -128,11 +128,11 @@
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/dropdown-menu-relative-button-emxn9u?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="相对于按钮触发器的下拉菜单"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -147,17 +147,17 @@
|
|||
|
||||
在这种方法中,菜单被渲染为 `<body>` 的直接子元素,并通过获取元素的 `offsetTop` 和 `offsetLeft` 相对于**页面**进行 `absolute` 定位,以获取 `<button>` 相对于页面的坐标,并添加其高度 (`offsetHeight`) 以获取渲染菜单的最终 Y 位置。
|
||||
|
||||
在 React 中,这可以使用 [React Portals](https://beta.reactjs.org/reference/react-dom/createPortal) 完成,它允许您在父组件的 DOM 层次结构之外进行渲染。Portals 的一个典型用例是当父组件具有 `overflow: hidden` 或 `z-index` 样式,但您需要子组件在视觉上“跳出”其容器时。常见的例子包括下拉菜单、工具提示、模态框。
|
||||
在 React 中,这可以使用 [React Portals](https://react.dev/reference/react-dom/createPortal) 来完成,它允许你在父组件的 DOM 层次结构之外进行渲染。Portals 的一个典型用例是当父组件具有 `overflow: hidden` 或 `z-index` 样式,但你需要子组件在视觉上“跳出”其容器。常见的例子包括下拉菜单、工具提示、模态框。
|
||||
|
||||
<iframe
|
||||
src="https://codesandbox.io/embed/dropdown-menu-relative-page-r4zoiu
|
||||
?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="使用 React Portal 实现的相对于页面的下拉菜单"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -175,11 +175,11 @@
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/dropdown-menu-relative-button-emxn9u?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="下拉菜单位置"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -221,11 +221,11 @@
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/dropdown-menu-autoflip-ybqbeu?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Dropdown Menu Autoflipping"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -267,11 +267,11 @@ document.addEventListener('touchstart', clickListener);
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/dropdown-menu-click-outside-lq040x?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="dropdown-menu-click-outside-lq040x"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
"eb927ffe",
|
||||
"902a3f0f",
|
||||
"b31ae6f2",
|
||||
"e21472e3",
|
||||
"541339b0",
|
||||
"2a7816d0",
|
||||
"b9f3fd8c",
|
||||
"bc88706e",
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
"eb927ffe",
|
||||
"902a3f0f",
|
||||
"b31ae6f2",
|
||||
"e21472e3",
|
||||
"541339b0",
|
||||
"2a7816d0",
|
||||
"b9f3fd8c",
|
||||
"bc88706e",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
### Flux 架构
|
||||
|
||||
建议使用类似 [Flux](https://facebook.github.io/flux/)/[Redux](https://redux.js.org/)/[Reducer](https://beta.reactjs.org/learn/scaling-up-with-reducer-and-context) 的架构,以将操作源从操作逻辑/实现中抽象出来。 一些操作可以通过与各种 UI 元素交互、由计时器定期触发或按键来触发。
|
||||
建议使用类似 [Flux](https://facebook.github.io/flux/)/[Redux](https://redux.js.org/)/[Reducer](https://react.dev/learn/scaling-up-with-reducer-and-context) 的架构,将动作源与动作逻辑/实现分离。 一些动作可以通过与各种 UI 元素交互、定时器定期触发或按键触发。
|
||||
|
||||
***
|
||||
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@
|
|||
"84fb1fa5",
|
||||
"65aca1ee",
|
||||
"1d91621f",
|
||||
"59dd3381",
|
||||
"a4ccc276",
|
||||
"3497a062",
|
||||
"126fece8",
|
||||
"ac12c0e",
|
||||
"445758ce",
|
||||
"452a0924",
|
||||
"f62c0050",
|
||||
|
|
@ -159,9 +159,9 @@
|
|||
"84fb1fa5",
|
||||
"65aca1ee",
|
||||
"1d91621f",
|
||||
"59dd3381",
|
||||
"a4ccc276",
|
||||
"3497a062",
|
||||
"126fece8",
|
||||
"ac12c0e",
|
||||
"445758ce",
|
||||
"452a0924",
|
||||
"f62c0050",
|
||||
|
|
|
|||
|
|
@ -110,23 +110,23 @@
|
|||
|
||||
#### 突破 DOM 层次结构
|
||||
|
||||
由于模态框显示在页面上方,并且不遵循页面元素的正常流程,因此渲染模态框比看起来更棘手。将模态框渲染在父 DOM 层次结构之外非常重要,因为如果父元素包含剪裁其内容的样式,则模态框内容可能无法完全可见。以下是 [React 文档](https://beta.reactjs.org/reference/react-dom/createPortal#rendering-a-modal-dialog-with-a-portal) 中的一个示例,演示了这个问题。
|
||||
由于模态框是在页面上显示的,并且不遵循页面元素的正常流程,因此渲染模态框比看起来更棘手。重要的是在父元素的 DOM 层次结构之外渲染模态框,因为如果父元素包含剪裁其内容的样式,则模态框内容可能无法完全可见。这是一个来自 [React 文档](https://react.dev/reference/react-dom/createPortal#rendering-a-modal-dialog-with-a-portal) 的示例,演示了这个问题。
|
||||
|
||||
<iframe
|
||||
src="https://codesandbox.io/embed/wnr51p?fontsize=14&hidenavigation=1&theme=dark&module=/App.js,/NoPortalExample.js,/PortalExample.js,/ModalContent.js,/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Modal Clipping Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
|
||||
/>
|
||||
|
||||
在 React 中,使用 [React Portals](https://beta.reactjs.org/reference/react-dom/createPortal) 可以实现在父组件的 DOM 层次结构之外进行渲染。Portals 的常见用例包括工具提示、下拉菜单、弹出框。
|
||||
在 React 中,可以使用 [React Portals](https://react.dev/reference/react-dom/createPortal) 在父组件的 DOM 层次结构之外进行渲染。Portals 的常见用例包括工具提示、下拉菜单、弹出框。
|
||||
|
||||
#### 遮罩层
|
||||
|
||||
|
|
@ -172,11 +172,11 @@
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/t1oldf?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Modal Example"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -229,11 +229,11 @@ document.addEventListener('touchstart', clickListener);
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/74fyqc?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Modal Dismiss On Click Outside"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -311,11 +311,11 @@ HTML 现在有一个原生的 `<dialog>` 元素,可以在创建模态对话框
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/imlco8?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Modal Animations"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
@ -327,11 +327,11 @@ HTML 现在有一个原生的 `<dialog>` 元素,可以在创建模态对话框
|
|||
<iframe
|
||||
src="https://codesandbox.io/embed/t3wwxr?fontsize=14&hidenavigation=1&theme=dark&module=/src/App.js,/src/styles.css&view=split"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
height: 500,
|
||||
border: 0,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
title="Modal Animations"
|
||||
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@
|
|||
"9b6ce116",
|
||||
"ef4e9c89",
|
||||
"31fb36e6",
|
||||
"de2467af",
|
||||
"6bbe24a8",
|
||||
"9e6d702",
|
||||
"aa63189d",
|
||||
"e9697260",
|
||||
|
|
@ -587,7 +587,7 @@
|
|||
"9b6ce116",
|
||||
"ef4e9c89",
|
||||
"31fb36e6",
|
||||
"de2467af",
|
||||
"6bbe24a8",
|
||||
"9e6d702",
|
||||
"aa63189d",
|
||||
"e9697260",
|
||||
|
|
|
|||
|
|
@ -729,7 +729,10 @@ In this section we will explain the entire update loop in detail, aka what happe
|
|||
3. The modified node and its chain of parent nodes are marked as dirty.
|
||||
4. Since the callbacks can call `editor.update(fn)` again, the editor maintains a queue of update callbacks to go through and the queue is processed in sequence.
|
||||
3. **Process transforms**: All registered transforms are called.
|
||||
4. **Reconciliation**: 5. If there are any dirty nodes, the reconcile function is called starting from the root node and recursively on all the child nodes. 6. For dirty nodes, the reconcile function updates the DOM based on the new `EditorNode`s. 7. As an optimization, reconciliation can be skipped if both new and old `EditorNode` instances for a `NodeKey` are the same and the node is not dirty. It means that nothing in the node or its entire subtree was modified.
|
||||
4. **Reconciliation**:
|
||||
1. If there are any dirty nodes, the reconcile function is called starting from the root node and recursively on all the child nodes.
|
||||
2. For dirty nodes, the reconcile function updates the DOM based on the new `EditorNode`s.
|
||||
3. As an optimization, reconciliation can be skipped if both new and old `EditorNode` instances for a `NodeKey` are the same and the node is not dirty. It means that nothing in the node or its entire subtree was modified.
|
||||
5. **Process listeners**: All registered listeners are called.
|
||||
|
||||
#### Reconciliation
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ interface TextNode extends EditorNode {
|
|||
* `<strong>Tarzan <u>and</u></strong><u> Jane</u>`
|
||||
* `<strong>Tarzan </strong><strong><u>and</u></strong><u> Jane</u>`
|
||||
|
||||
这种方法的主要缺点在于,存在多种渲染格式化文本的 HTML 的方法,并且当文本具有更多格式(例如斜体或删除线)时,嵌套的方式也可能不同。
|
||||
这种方法的主要缺点在于,存在多种渲染格式化文本的 HTML 的方法,并且当文本具有更多格式(例如斜体或删除线)时,嵌套的方式也可能不同。
|
||||
|
||||
前两种方法使用嵌套标签进行格式化,当编辑已格式化的文本以添加/删除格式时,这可能会成为一个问题,编辑器需要引入更多元素进行格式化,或者在删除格式时删除正确的元素(甚至可能将它们组合起来)。考虑到复合格式的可能组合数量,正确实现编辑非常复杂。
|
||||
|
||||
|
|
@ -715,15 +715,18 @@ interface EditorSelection {
|
|||
|
||||
在本节中,我们将详细解释整个更新循环,也就是当调用`editor.update(fn)`时会发生什么,无论是通过用户DOM事件(例如`input`、`keypress`)还是以编程方式调度的命令(例如来自工具栏)。
|
||||
|
||||
1. **克隆状态**:编辑器对内容状态(即节点映射`Map<NodeKey, EditorNode>`)进行**浅克隆**。 创建一个单独的`Map`实例,但这些值都指向原始的`EditorNode`。
|
||||
2. **处理更新**:传递给`editor.update(fn)`的回调被调用
|
||||
1. 只要`EditorNode`被修改,它们就会在修改前被克隆。
|
||||
2. 克隆的内容状态将被更新以指向新的节点实例。
|
||||
1. **克隆状态**:编辑器对内容状态进行**浅克隆**,即节点映射 (`Map<NodeKey, EditorNode>`)。创建一个单独的 `Map` 实例,但值都指向原始的 `EditorNode`。
|
||||
2. **处理更新**:传递给 `editor.update(fn)` 的回调被调用
|
||||
1. 只要 `EditorNode` 被修改,它们首先会被克隆,然后再修改。
|
||||
2. 克隆的内容状态将被更新,以指向新的节点实例。
|
||||
3. 修改后的节点及其父节点链被标记为脏。
|
||||
4. 由于回调可以再次调用`editor.update(fn)`,编辑器会维护一个更新回调队列,并按顺序处理该队列。
|
||||
3. **处理转换**:调用所有已注册的转换。
|
||||
4. **协调**:5. 如果有任何脏节点,则从根节点开始并在所有子节点上递归调用协调函数。 6. 对于脏节点,协调函数根据新的`EditorNode`更新DOM。 7. 作为优化,如果`NodeKey`的新的和旧的`EditorNode`实例相同并且该节点未被修改,则可以跳过协调。 这意味着该节点或其整个子树中没有任何内容被修改。
|
||||
5. **处理监听器**:调用所有已注册的监听器。
|
||||
4. 由于回调可以再次调用 `editor.update(fn)`,编辑器维护一个更新回调队列并按顺序处理。
|
||||
3. **处理转换**:所有已注册的转换都被调用。
|
||||
4. **协调**:
|
||||
1. 如果有任何脏节点,协调函数将从根节点开始调用,并在所有子节点上递归调用。
|
||||
2. 对于脏节点,协调函数根据新的 `EditorNode` 更新 DOM。
|
||||
3. 作为优化,如果 `NodeKey` 的新旧 `EditorNode` 实例相同且节点未脏,则可以跳过协调。这意味着节点或其整个子树中没有任何内容被修改。
|
||||
5. **处理监听器**:所有已注册的监听器都被调用。
|
||||
|
||||
#### 协调
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue