Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: homepage #55

Merged
merged 88 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
7171fc9
chore: add docs
zhangyuang Apr 6, 2021
e522b29
chore: add docs
zhangyuang Apr 6, 2021
5d31b8f
chore: docs (#46)
dellyoung Apr 12, 2021
3ac0b99
chore: docs
yiming29 Apr 13, 2021
34eb0f7
docs: update docs
zhangyuang Apr 14, 2021
a3d7936
update
zhangyuang Apr 14, 2021
e1c8663
update
zhangyuang Apr 14, 2021
7955eb1
chore: update
zhangyuang Apr 14, 2021
388fe77
chore: docs code light
yiming29 Apr 17, 2021
dce3815
chore: md文档code样式优化
yiming29 Apr 18, 2021
ab63650
chore: md文档code样式优化
yiming29 Apr 18, 2021
57aa465
chore: md组件侧边菜单done
yiming29 Apr 18, 2021
c01fcb9
update
zhangyuang Apr 19, 2021
35f0417
update
zhangyuang Apr 19, 2021
2f6c459
update
zhangyuang Apr 19, 2021
00c7972
update
zhangyuang Apr 19, 2021
ea0a579
chore: 字体优化
yiming29 Apr 19, 2021
bc70266
feat: add vite-raw-plugin
zhangyuang Apr 19, 2021
a60e1d3
chore: table css
yiming29 Apr 19, 2021
04c90cb
chore: table css
yiming29 Apr 19, 2021
bc7e266
chore: add f.yml
zhangyuang Apr 19, 2021
5405f1b
chore: update config
zhangyuang Apr 19, 2021
21a0d79
docs: update
zhangyuang Apr 19, 2021
3c6dc8e
chore: add docs vite.config
zhangyuang Apr 19, 2021
1593401
docs: 文档更新
zhangyuang Apr 19, 2021
03798b3
chore: add develop & started
Apr 20, 2021
53b4f4e
docs: update demo.md
zhangyuang Apr 20, 2021
a37dbea
chore: docs css
yiming29 Apr 20, 2021
7fa80bf
Merge branch 'doc/homepage' of github.com:ykfe/ssr into doc/homepage
yiming29 Apr 20, 2021
aada738
chore: docs css
yiming29 Apr 20, 2021
6d60383
docs: update demo.md
zhangyuang Apr 20, 2021
6ccc4b7
docs: 完善文档
zhangyuang Apr 20, 2021
cc39fbb
chore: docs css
yiming29 Apr 20, 2021
d041b5c
Merge branch 'doc/homepage' of github.com:ykfe/ssr into doc/homepage
yiming29 Apr 20, 2021
77791dc
docs: add fetch.md
zhangyuang Apr 20, 2021
2b3d580
feat: 官网首页
Apr 21, 2021
566a63b
Revert "chore: add develop & started"
Apr 20, 2021
4bd7441
feat: 官网首页
Apr 21, 2021
c3f558c
chore: update
zhangyuang Apr 21, 2021
002605c
docs: update readme.md
zhangyuang Apr 21, 2021
adc279b
docs: update
zhangyuang Apr 21, 2021
6f3dbd3
feat: 官网首页样式
Apr 21, 2021
93debd7
docs: update
zhangyuang Apr 21, 2021
213221f
docs: update
zhangyuang Apr 21, 2021
33bd406
fix: menu
yiming29 Apr 21, 2021
5ab4064
feat: 适配移动端
yiming29 Apr 21, 2021
8fa6b42
chore: 组件通信
yiming29 Apr 21, 2021
91de301
docs: update plugin.md
zhangyuang Apr 21, 2021
85ac3c2
chore: 组件通信
yiming29 Apr 22, 2021
7c8dd03
Merge branch 'doc/homepage' of github.com:ykfe/ssr into doc/homepage
yiming29 Apr 22, 2021
1ec9ed3
chore: 组件通信
yiming29 Apr 22, 2021
e56bfb9
chore: 组件通信
yiming29 Apr 22, 2021
fec52a1
chore: 组件通信
yiming29 Apr 22, 2021
b458832
docs: add config.md
zhangyuang Apr 22, 2021
7c6212a
chore: update
zhangyuang Apr 22, 2021
03e36ea
chore: update
zhangyuang Apr 22, 2021
a815655
chore: update
zhangyuang Apr 22, 2021
d361deb
feat: 上一篇/下一篇 逻辑优化
yiming29 Apr 22, 2021
672542d
feat: 上一篇/下一篇 逻辑优化
yiming29 Apr 22, 2021
70d6ffb
feat: 首页
Apr 23, 2021
fc95f47
fix: 🐛 header 路由高亮 (#54)
SuperHuangXu Apr 24, 2021
d667518
docs: update config
zhangyuang Apr 24, 2021
3c646d3
feat: 移动端适配
Apr 24, 2021
164e0f6
docs: update config
zhangyuang Apr 24, 2021
463dbd8
docs: update config
zhangyuang Apr 24, 2021
17ee531
fix: 样式fix
yiming29 Apr 24, 2021
72ab36c
Merge branch 'doc/homepage' of github.com:ykfe/ssr into doc/homepage
yiming29 Apr 24, 2021
f41e3a7
docs: update config
zhangyuang Apr 24, 2021
d68d037
docs: update config
zhangyuang Apr 24, 2021
3f5be3f
docs: update config
zhangyuang Apr 24, 2021
52b6c93
feat:补充vite文档
Apr 24, 2021
e0677ec
docs: update config
zhangyuang Apr 24, 2021
62aae7d
feat: home page update
Apr 24, 2021
955e43b
docs: update communication.md
zhangyuang Apr 24, 2021
f51aa51
chore: add csr
Apr 24, 2021
77c8db1
Merge branch 'doc/homepage' of https://github.com/ykfe/ssr into doc/h…
Apr 24, 2021
b4a4eca
docs: update communication.md
zhangyuang Apr 24, 2021
bfae65e
docs: update communication.md
zhangyuang Apr 24, 2021
3609d7f
docs: update img
zhangyuang Apr 24, 2021
38fdb75
docs: update img
zhangyuang Apr 24, 2021
3ac7146
docs: update img
zhangyuang Apr 24, 2021
e3d6635
docs: update img
zhangyuang Apr 24, 2021
c6c2cb5
docs: update img
zhangyuang Apr 24, 2021
437499f
docs: update img
zhangyuang Apr 24, 2021
65e2a9b
docs: update img
zhangyuang Apr 24, 2021
8a5725c
feat: update style
Apr 25, 2021
24e6fa4
fix: docs菜单优化
yiming29 Apr 25, 2021
8d89c77
chore: update homepage
zhangyuang Apr 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
docs: update communication.md
  • Loading branch information
zhangyuang committed Apr 24, 2021
commit 955e43bd8cdf1c118eb4ab4adc5044aa55be5ac2
146 changes: 32 additions & 114 deletions docs/web/markdown/features/communication.md
Original file line number Diff line number Diff line change
@@ -1,125 +1,39 @@
# 组件通信
众所周知,在组件式开发中,最大的痛点就在于组件之间的通信。本篇文章将会分别对`vue`和`react`讲述如何进行组件通信。

> 在阅读文档之前,你应该已经熟悉了这两个[目录结构](/docs/features$structure)和[数据获取](/docs/features$fetch)。
数据管理是前端开发中重要的一环知识。在组件层级过多时通过 `props` 传递非常的苦难,我们通常会使用额外的数据管理库来进行数据管理。同样数据管理从最初的 `flux` 架构到现在最新的 `context` 思路经过了无数变迁,在业界也有着非常非常多的方案。本章节将讲述在 `ssr` 框架中我们如何进行数据管理

## Vue 场景
在 Vue 中,Vue 提供了各种各样的组件通信方式
- 父子组件通信的`props/$emit`
- 全局数据管理的`Vuex`。
- vue3新提供的`provide/inject`
### Vuex
在需要全局状态管理的场景下,可以使用`Vuex`,它可以:
- 集中式存储管理应用的所有组件的状态
- 保证状态以可预测的方式发生变化
- 与调试工具集成,提供功能:time-travel、状态快照导入导出

下面用[示例](https://github.com/ykfe/ssr/tree/dev/example/midway-vue3-ssr)介绍在本框架中使用Vuex。
> 在阅读本章节之前,请确保你已经阅读并熟悉这两个章节的内容[目录结构](/docs/features$structure)和[数据获取](/docs/features$fetch)。

1.创建`store/index.tx`文件,用于整体导出`store`
```javascript
// web/store/index.tx
import { indexStore } from './modules/index'
## 发展历史

const modules = {
indexStore,
}
export {
modules
}
```
数据管理方案从最早的 `flux` 架构提出,即 `视图层组件不允许直接修改应用状态,只能触发 action。应用的状态必须独立出来放到 store 里面统一管理,通过侦听 action 来执行具体的状态操作`。也就是大家熟知的 `单向数据流`。当然真实应用中,我们不可能所有的状态都放在 `store` 中,组件仍然可以拥有并且直接修改自己的 `私有状态`。

2.创建`store/modules/index.ts`文件,作为首页对应`store`
```javascript
// web/store/modules/index.ts
const indexStore = {
namespaced: true,
state: {
data: {}
},
mutations: {
setData (state, payload) {
state.data = payload.data
}
},
actions: {
initialData ({ commit }, { payload }) {
commit('setData', payload)
}
}
}
实现 `单向数据流` 又分为两大派系。

export {
indexStore
}
```
分别是 `immutable` 思想的 `react-redux`, `redux-(thunk|sage)`, `dva`, `redux-toolkit` 等等

3.获取并同步数据到`store`中
在页面对应的`fetch.ts`文件中获取到数据后,可以通过调用`store.dispatch()`方法,将数据同步到`store`中。
以及基于 `observer` 思想的 `mobx`, `vuex` 等等

```javascript
// web/pages/index/fetch.ts
import { ISSRContext } from 'ssr-types'
import { IndexData } from '@/interface'
interface IApiService {
index: () => Promise<IndexData>
}
也有些开发者认为 `React+Mobx`,就是类型友好的干净版 `Vue`, 虽然上述方案没有绝对的优劣之分。但从开发者体验的角度来看基于 `observer` 思想实现的方案在编写舒适度上是要更优的。

export default async ({ store, router }, ctx?: ISSRContext<{
apiService?: IApiService
}>) => {
const data = __isBrowser__ ? await (await window.fetch('/api/index')).json() : await ctx?.apiService?.index()
await store.dispatch('indexStore/initialData', { payload: data })
}
```
由于数据管理没有唯一答案,所以在 `ssr` 框架中我们`可能`会在框架层面提供多种方案让用户进行选择。但我们始终建议使用框架默认支持的方案,不要自行引入外部方案。我们也会不断的完善这一块的内容。

4.组件通过`store`获取数据
在组件中加入`Vuex`相关方法,可获取到`store`中的数据
## Vue 场景解决方案

```javascript
// web/pages/index/render.ts
<template>
<div>
.....
<Slider :data="indexData[0].components" />
.....
</div>
</template>
在 `Vue` 场景中,我们提供了大家熟知的 [Vuex](https://vuex.vuejs.org/) 作为解决方案。另外,在 `Vue3` 场景中,我们额外提供了 [Provide/Inject](https://v3.cn.vuejs.org/guide/composition-api-provide-inject.html#%E4%BF%AE%E6%94%B9%E5%93%8D%E5%BA%94%E5%BC%8F-property) 方案来帮助各位简化这一功能。

<script>
import { mapState } from 'vuex'
import Slider from '@/components/slider'

export default {
components: {
Slider,
},
computed: {
...mapState({
indexData: state => state.indexStore?.data
})
}
}
</script>
```
### Vuex

`Vuex` 的具体使用方案,开发者可以查看它的官方文档。这里不进行赘述。在[数据获取](/docs/features$fetch)章节中,我们提出了用 `fetch.ts` 来进行数据的获取。在 `fetch.ts` 中我们可以拿到 `vuex` 的实例来进行相关操作
### Provide/Inject
在 `Vue3` 中我们提供了另一种更加轻量级的跨组件数据共享的方式,也就是 `Provide/Inject` , `Vuex` 和 `provide/inject` 主要的区别在于, `Vuex` 中的全局状态的每次修改是可以追踪回溯的,而 `provide/inject` 中变量的修改是无法控制的,换句话说,你不知道是哪个组件修改了这个全局状态。若你完全不考虑使用 `Vuex` 来做数据管理的话,那么你可以不使用默认的示例`Vuex`全部有关代码,但暂时不要删除 `store` 的入口文件,后续会底层兼容不存在 `store` 文件的情况。

在渲染的过程中,我们会将 `layout fetch` 与 `page fetch` 的 `返回数据` 组合后以 `props` 的形式注入到 `layout/App.vue` 当中,开发者可以在该文件当中 `provide` 如下所示。便可以在任意组件中通过 `inject` 拿到该数据并且可以修改数据自动触发更新,为了防止应用数据混乱,我们建议为不同的组件返回数据添加不同的 `namespace` 命名空间。同样当路由切换时我们也会自动的将 `fetch.ts` 返回的数据合并进 `asyncData`。
在 `Vue3` 中我们提供了另一种更加轻量级的跨组件数据共享的方式,也就是 `Provide/Inject` , `Vuex` 和 `provide/inject` 主要的区别在于, `Vuex` 中的全局状态的每次修改是可以追踪回溯的,而 `provide/inject` 中变量的修改是无法控制的,换句话说,你不知道是哪个组件修改了这个全局状态。

为了防止对象失去响应性,这里我们 follow `ref 对象`的规则。将真正的数据对象存放在 `asyncData.value` 字段中。并且将整个 `asyncData` 转换为响应式。这样我们后续可以直接通过修改 `asyncData.value = obj ` 或者 `asyncData.value.key = obj` 的方式来修改数据仍然可以让对象保持响应式。使用这种方式需要注意的是如果在 `template` 中使用的话仍然需要添加 `.value` 取值不会自动展开。
在中小型应用中,若你完全不考虑使用 `Vuex` 来做数据管理的话,那么你可以不使用默认的示例 `Vuex` 全部有关代码,但暂时不要删除 `store` 的入口文件,后续会底层兼容不存在 `store` 文件的情况。

```javascript
// fetch.ts
export default () => {
return {
indexData: {}
}
}
```
在渲染的过程中,我们会将 `layout fetch` 与 `page fetch` 的 `返回数据` 组合后以 `props` 的形式注入到 `layout/App.vue` 当中,开发者可以在该文件当中 `provide` 如下所示。

```javascript
```html
// layout/App.vue
<script>
import { reactive, provide } from 'vue'
Expand All @@ -138,7 +52,11 @@ export default {
</script>
```

```javascript
便可以在任意组件中通过 `inject` 拿到该数据并且可以修改数据自动触发更新,为了防止应用数据混乱,我们建议为不同的组件返回数据添加不同的 `namespace` 命名空间。同样当路由切换时我们也会自动的将 `fetch.ts` 返回的数据合并进 `asyncData`。

为了防止对象失去响应性,这里我们 follow `ref 对象`的规则。将真正的数据对象存放在 `asyncData.value` 字段中。并且将整个 `asyncData` 转换为响应式。这样我们后续可以直接通过修改 `asyncData.value = obj ` 或者 `asyncData.value.key = obj` 的方式来修改数据仍然可以让对象`保持响应式`。使用这种方式需要注意的是如果在 `template` 中使用的话仍然需要添加 `.value` 取值不会自动展开。

```html
// 任意组件
<template>
{{ asyncData.value }}
Expand Down Expand Up @@ -166,13 +84,13 @@ export default {

## React 场景

### Mobx
在 `React` 场景中,我们没有使用上述的任何一种数据管理方案,我们采用了思想上与 `Provide/Inject` 类似的,同样也是 [react-hooks](https://reactjs.org/docs/hooks-intro.html) 出现后出现在大家视野的 [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext)

### Hooks

随着 hooks 的流行以及 [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext) 这个 API 的推出, 越来越多的开发者希望用它来代替 Dva, Redux-Toolkit 这些方案来实现数据管理,因为之前的数据管理方案写起来实在是太累了。
先说结论:useContext + useReducer 不能完全代替 Dva 的功能。严格来说,它只实现了组件共享 store,以及触发 action 修改 store 的能力,对于异步操作的顺序性则需要开发者自行控制。本框架没有使用任何基于 hooks 新造的第三方轮子来做数据通信,仅使用 React 提供的最原始的 API 来实现跨组件通信。如果你只是想进行跨组件通信,以及数据的自动订阅能力,你完全不需要 Redux。
此功能在中小型应用的开发过程中完全够用,大型应用可能需要考虑拆分成多个 Context.Provider 的组织形式。后续我们会继续跟进最佳实践
随着 `hooks` 的流行以及 `useContext` 这个 API 的推出, 越来越多的开发者希望用它来代替 `Dva`, `Redux` 这些方案来实现数据管理,因为之前的数据管理方案写起来实在是太累了。

先说结论:`useContext + useReducer` 不能完全代替 `Redux` 的功能。但对于大多数应用来说它已足够够用。本框架没有使用 `任何` 基于 hooks 新造的第三方轮子来做数据通信,仅使用 `React` 提供的最原始的 `API` 来实现跨组件通信。如果你只是想进行跨组件通信,以及数据的自动订阅更新能力,你完全不需要 `Redux`。

通过使用 `useContext` 来获取全局的 `context`, `useContext` 返回两个值分别为

Expand Down Expand Up @@ -213,8 +131,6 @@ function Search (props) {
}
return (
<div className={styles.searchContainer}>
{/* 这里需要给 value 一个兜底的状态 否则 context 改变 首次 render 的 text 值为 undefined 会导致 input 组件 unmount */}
{/* ref: https://stackoverflow.com/questions/47012169/a-component-is-changing-an-uncontrolled-input-of-type-text-to-be-controlled-erro/47012342 */}
<input type="text" className={styles.input} value={state.search?.text ?? ''} onChange={handleChange} placeholder="该搜索框内容会在所有页面共享"/>
<img src="https://img.alicdn.com/tfs/TB15zSoX21TBuNjy0FjXXajyXXa-48-48.png" alt="" className={styles.searchImg} onClick={toSearch}/>
</div >
Expand All @@ -227,14 +143,16 @@ export default Search

> 注: 以上只为示例,实际开发中我们只推荐在跨组件通信时使用 dispatch,局部状态应该使用 useState 来实现,否则会导致函数内部状态过于复杂,难以追踪。

关于更多 hooks 使用的最佳实践可以参考该[文章](https://zhuanlan.zhihu.com/p/81752821)
关于更多 `hooks` 使用的最佳实践可以参考该[文章](https://zhuanlan.zhihu.com/p/81752821)

我们只有一个最顶层的 store,以及一个 reducer 来修改这个 store。综上本方案的优点以及不足如下
我们只有一个最顶层的 `store`,以及一个 `reducer` 来修改这个 `store`。综上本方案的优点以及不足如下

优势:
- 无需再编写繁琐的 store,也不需要拆分多个 provider
- 无需再编写繁琐的单向数据流相关代码
- 共享全局状态以及修改全局状态非常简单自然

不足
- 在大型应用状态复杂的情况下,比较难以管理
- 需要配合 useMemo 一起使用,否则容易导致性能问题 (只要是使用了 useContext 都会遇到该问题)
- 需要配合 `useMemo` 一起使用,否则容易导致性能问题 (只要是使用了 `useContext` 都会遇到该问题)

在大型应用可能需要考虑拆分成多个 `Context.Provider` 的组织形式来避免意外的重新 `render`。后续我们会继续跟进最佳实践
36 changes: 33 additions & 3 deletions docs/web/markdown/features/fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Foo extends React.component {
页面级别的 `fetch` (可选),定义在 `web/pages/xxx/fetch.ts` 路径

意义: 页面级别的 `fetch` 将会在当前访问该前端页面组件对应的 `path` 时被调用
### 调用时机
### fetch 调用时机

这里我们将其分为`服务端渲染模式`和`客户端渲染模式`两种情况

Expand All @@ -79,13 +79,43 @@ class Foo extends React.component {

![](/images/csr-fetch.png)

#### 注意
### 方法入参

在 `Vue`, `React` 场景以及 `服务端`,`客户端` 环境我们的 `fetch.ts` 的入参会有稍许不同

#### Vue 场景

在 `Vue` 场景中,我们将会把 `vuex`, `vue-router` 返回的示例作为参数传入。开发者可以在任何时候使用它们。在 `服务端` 环境,我们会额外把当前请求的上下文 `ctx` 传入。开发者可以通过 `ctx` 拿到上面挂载的 `自定义 Service` 或者 `ctx.request` 等对象信息。这取决于服务端代码调用 `core` 模块时的具体入参实现。

```js
import { ISSRContext } from 'ssr-types'

export default async ({ store, router }, ctx?: ISSRContext) => {
const data = __isBrowser__ ? await (await window.fetch('/api/index')).json() : await ctx?.apiService?.index()
await store.dispatch('indexStore/initialData', { payload: data })
}
```

#### React 场景

在 `React` 场景,我们在 `服务端` 会将当前请求的上下文 `ctx` 作为参数传入。开发者可以通过 `ctx` 拿到上面挂载的 `自定义 Service` 或者 `ctx.request` 等对象信息。这取决于服务端代码调用 `core` 模块时的具体入参实现。在前端路由切换时,也就是客户端 `fetch` 数据场景。我们会将 `react-router` 提供的[路由元信息](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d90beb2f67881d54384c0f9b42a03233aaba1ca1/types/react-router/index.d.ts#L69)作为参数传入

```js
export default async params => {
const data = __isBrowser__ ? await (await window.fetch(`/api/detail/${(ctx as RouteComponentProps<{id: string}>).match.params.id}`)).json() : await ctx.apiDeatilservice.index(ctx.params.id)
return {
// 建议根据模块给数据加上 namespace防止数据覆盖
detailData: data
}
}
```
### 注意

上述图片指的是用 `前端路由` 进行跳转的情况。此时的跳转并不会真正的向服务端发起请求。所以数据的获取是在客户端完成的。

如果开发者使用 `a` 标签进行跳转。则此时可视为完全打开一个新页面。此时的数据获取操作仍然是在服务端完成

#### 框架差异
### 不同场景实现差异

在 `React` 场景以及 `Vue` 场景我们的切换路由 `fetch` 数据的时机略有不同。之所以会有差异这里是为了选择不同框架实现起来最简单好用的方式。

Expand Down
2 changes: 1 addition & 1 deletion docs/web/markdown/features/vite.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const render = (await vite.ssrLoadModule('/src/entry-server.js')).render
之前创建的模板应用只需以下三步便可接入 Vite

- 安装最新版本的插件依赖 `version >= 5.5.1`
- layout/index.vue 中添加 `<slot name="viteClient" />` 参考该[文件](https://github.com/ykfe/ssr/blob/dev/example/midway-vue3-ssr/web/components/layout/index.vue)
- `layout/index.vue` 中添加 `<slot name="viteClient" />` 参考该[文件](https://github.com/ykfe/ssr/blob/dev/example/midway-vue3-ssr/web/components/layout/index.vue)
- 服务端应用启动时中间件初始化改为 `async await` 形式, 参考该[文件](https://github.com/ykfe/ssr/blob/dev/example/midway-vue3-ssr/src/app.ts#L11)

### React 应用迁移
Expand Down