Skip to content

Commit 2b388d3

Browse files
committed
feat: redux数据同构
1 parent d50e005 commit 2b388d3

File tree

9 files changed

+132
-113
lines changed

9 files changed

+132
-113
lines changed

build/client/utils.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,14 @@ exports.styleLoaders = function(options) {
5959
]
6060
})
6161

62-
// output.push({
63-
// test: /\.css$/,
64-
// include: /node_modules/,
65-
// use: [
66-
// options.extract ? MiniCssExtractPlugin.loader : 'style-loader',
67-
// { loader: 'css-loader' }
68-
// ]
69-
// })
62+
output.push({
63+
test: /\.css$/,
64+
include: /node_modules/,
65+
use: [
66+
options.extract ? MiniCssExtractPlugin.loader : 'style-loader',
67+
{ loader: 'css-loader' }
68+
]
69+
})
7070

7171
return output
7272
}

src/client/components/container/index.tsx

+29-57
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,70 @@
1-
// 高阶组件 用于提取重复逻辑
2-
1+
//高阶组件 用于提取重复逻辑
32
import React from 'react'
43

5-
let _this: any = null
6-
let _isPop = false //是否触发过popState
7-
let _isMount = false //组件是否挂载完成
4+
let _this = null
5+
86
const popStateCallback = () => {
9-
// 使用 popStateCallback 保存函数防止 addEventListener 重复注册
7+
// 使用popStateFn保存函数防止addEventListener重复注册
108
if (_this && _this.getInitialProps) {
119
console.log('popStateFn')
12-
_isPop = true
13-
if (_isMount) {
14-
//只有当前组件挂载后才能执行数据预取,否则会报错
15-
_this.getInitialProps()
16-
}
10+
_this.getInitialProps()
1711
}
1812
}
1913

20-
export default (SourceComponent: any) => {
21-
return class HoComponent extends React.Component<any, any> {
22-
constructor(props: any) {
14+
export default SourceComponent => {
15+
return class HoComponent extends React.Component {
16+
constructor(props, context) {
2317
super(props)
24-
18+
console.log('props', props)
2519
this.state = {
2620
initialData: {},
27-
canClientFetch: false // 浏览器端是否需要请求数据
21+
canClientFetch: false //浏览器端是否需要请求数据
2822
}
2923
}
30-
// 用于服务端调用
31-
static async getInitialProps(ctx: any) {
24+
25+
//转接子组件的预取方法,服务端会调用这个方法来做数据预取
26+
static async getInitialProps(ctx) {
3227
return SourceComponent.getInitialProps
3328
? await SourceComponent.getInitialProps(ctx)
3429
: {}
3530
}
36-
37-
//用于封装处理
31+
// ! csr/ssr切换路由时才调用组件的getInitialProps方法
32+
//用于封装处理数据的更新逻辑
3833
async getInitialProps() {
3934
// ssr首次进入页面以及csr/ssr切换路由时才调用组件的getInitialProps方法
40-
const { match, location } = this.props
35+
const props = this.props
36+
const store = window.__STORE__ //从全局得到 store
37+
38+
// 兼容不使用 redux 的页面
39+
// 通过props.getInitialData判断
40+
// ! 那就说明约定getInitialData
4141
const res = SourceComponent.getInitialProps
42-
? await SourceComponent.getInitialProps({ match, location })
42+
? await SourceComponent.getInitialProps({ store })
4343
: {}
44-
this.setState({
45-
initialData: res,
46-
canClientFetch: true
47-
})
48-
49-
console.log('getInitialProps')
50-
let { tdk } = res.page
44+
//处理页面 title 显示
45+
let { tdk } = res.page || {}
5146
if (tdk) {
5247
document.title = tdk.title
5348
}
5449
}
5550

5651
async componentDidMount() {
57-
//注册事件,用于在页面回退和前进的时候触发
58-
_isMount = true //组件挂载完成
59-
// @ts-ignore
60-
if (window.__IS__SSR__) {
61-
//只有当启用 ssr 时
62-
_this = this // 修正_this指向,保证_this指向当前渲染的页面组件
63-
//注册事件
64-
window.addEventListener('popstate', popStateCallback)
65-
66-
if (_isPop) {
67-
//如果前进或者后退 则需要异步获取数据
68-
this.getInitialProps()
69-
}
70-
}
52+
_this = this // 修正_this指向,保证_this指向当前渲染的页面组件
53+
//注册事件,用于在页面回退的时候触发
54+
window.addEventListener('popstate', popStateCallback)
7155

7256
const canClientFetch = this.props.history && this.props.history.action === 'PUSH' //路由跳转的时候可以异步请求数据
73-
console.log('canClientFetch', canClientFetch)
74-
// @ts-ignore
75-
if (canClientFetch || !window.__IS__SSR__) {
57+
if (canClientFetch) {
58+
//如果是 history PUSH 操作 则更新数据
7659
await this.getInitialProps()
7760
}
7861
}
7962

80-
componentWillUnmount() {
81-
console.log('unmount')
82-
_isPop = false //重置为未触发
83-
_isMount = false //重置为未挂载
84-
}
85-
8663
render() {
87-
// 只有在首次进入页面需要将window.__INITIAL_DATA__作为props,路由切换时不需要
88-
8964
const props = {
9065
initialData: {},
9166
...this.props
9267
}
93-
// @ts-ignore
9468

9569
if (__SERVER__) {
9670
//服务端渲染
@@ -101,9 +75,7 @@ export default (SourceComponent: any) => {
10175
//需要异步请求数据
10276
props.initialData = this.state.initialData || {}
10377
} else {
104-
// @ts-ignore
10578
props.initialData = window.__INITIAL_DATA__
106-
// @ts-ignore
10779
window.__INITIAL_DATA__ = null //使用过后清除数据,否则其他页面会使用
10880
}
10981
}

src/client/main/index.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@ import proConfig from '../../common/pro-config'
1515
import StyleContext from 'isomorphic-style-loader/StyleContext'
1616
import { Provider } from 'react-redux'
1717

18-
import store from '@/store/reducers'
18+
import getStore from '@/store/reducers'
1919

20-
function renderDom(routeList: any) {
20+
function renderDom(routeList: any, initStoreState?: any) {
2121
// @ts-ignore
2222
const insertCss = (...styles) => {
2323
const removeCss = styles.map(style => style._insertCss()) // 客户端执行,插入style
2424
return () => removeCss.forEach(dispose => dispose()) // 组件卸载时 移除当前的 style 标签
2525
}
26-
// 渲染index
26+
// !redux数据更新
27+
const store = getStore(initStoreState)
28+
// 服务端只需要获取state就行 我们方法的定义在redux就定义好了
29+
console.log('同步更新的客户端store', store.getState())
30+
// @ts-ignore
31+
window.__STORE__ = store
2732
// @ts-ignore
2833
const renderMethod = module.hot ? ReactDOM.render : ReactDOM.hydrate
2934
renderMethod(
@@ -39,21 +44,28 @@ function renderDom(routeList: any) {
3944
}
4045

4146
function clientRender(routeList: any) {
47+
let initialData: any = null
48+
let initStoreState: any = null
4249
if (document.getElementById('ssrTextInitData')) {
4350
// @ts-ignore
4451
let value = document.getElementById('ssrTextInitData').value
45-
let initialData = JSON.parse(value && value.replace(/\\n/g, ''))
52+
initialData = JSON.parse(value && value.replace(/\\n/g, ''))
4653
// @ts-ignore
4754
window.__INITIAL_DATA__ = initialData || {}
4855
}
4956

57+
if (document.getElementById('ssrTextInitStoreData')) {
58+
// @ts-ignore
59+
let value = document.getElementById('ssrTextInitStoreData').value
60+
initStoreState = JSON.parse(value && value.replace(/\\n/g, ''))
61+
}
62+
5063
//查找路由
5164
let matchResult = matchRoute(document.location.pathname, routeList)
5265
let { targetRoute } = matchResult
5366
if (targetRoute) {
5467
//预加载 等待异步脚本加载完成
5568
if (targetRoute.component[proConfig.asyncComponentKey]) {
56-
console.log('targetRoute===>', targetRoute)
5769
targetRoute
5870
.component()
5971
.props.load()
@@ -62,11 +74,10 @@ function clientRender(routeList: any) {
6274
console.log('异步组件加载完成.')
6375
//设置已加载完的组件,否则需要重新请求
6476
// targetRoute.component = res ? res.default : null
65-
renderDom(routeList)
77+
renderDom(routeList, initStoreState)
6678
})
6779
}
6880
} else {
69-
console.log('renderDom==>', renderDom)
7081
renderDom(routeList)
7182
}
7283
}

src/client/main/layout.tsx

+15-12
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
import React from 'react';
2-
import { Link, NavLink } from 'react-router-dom';
3-
import { withRouter } from 'react-router';
4-
import { hot } from 'react-hot-loader/root';
1+
import React from 'react'
2+
import { Link, NavLink } from 'react-router-dom'
3+
import { withRouter } from 'react-router'
4+
import { hot } from 'react-hot-loader/root'
55

6-
import css from './layout.less';
6+
// @ts-ignore
7+
import css from './layout.less'
78

9+
// @ts-ignore
810
import withStyles from 'isomorphic-style-loader/withStyles'
911

1012
class Index extends React.Component {
1113
constructor(props: any) {
12-
super(props);
14+
super(props)
1315
}
1416

15-
1617
render() {
17-
return <div className="layout-box">
18-
<h1>koa+react+ssr</h1>
19-
{this.props.children}
20-
</div>
18+
return (
19+
<div className="layout-box">
20+
<h1>koa+react+ssr</h1>
21+
{this.props.children}
22+
</div>
23+
)
2124
}
2225
}
2326

24-
export default withStyles(css)(hot(Index));
27+
export default withStyles(css)(hot(Index))

src/client/modules/home/index.tsx

+24-15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import isConnect from '@/library/isConnect'
66

77
console.log('css', css)
88
export const Home: FC<any> = (props: any) => {
9+
console.log('props', props)
910
const [nums, setNums] = useState<number[]>([1, 2, 3, 4, 5, 6])
1011
const handleClick = () => {
1112
window.alert('handleClick')
@@ -14,31 +15,39 @@ export const Home: FC<any> = (props: any) => {
1415
<div className="home">
1516
<h1>hello react-ssr</h1>
1617
<button onClick={handleClick}>click me1的顶顶顶顶顶的</button>
17-
{
18-
[111, 2, 3, 4, 5, 6111, 17, 338, 1232221].map((item: number) => <div key={item}>
19-
{item}
20-
</div>)
21-
}
18+
{[111, 2, 3, 4, 5, 6111, 17, 338, 1232221].map((item: number) => (
19+
<div key={item}>{item}</div>
20+
))}
2221
<Link to={'/history'}>2222clqick to2 404 page12为</Link>
2322
</div>
2423
)
2524
}
2625
// @ts-ignore
26+
// ! 约定 服务端会调用这个方法 ===> 相当于是生命周期
27+
// 这一块可能会设计到把redux的操作
2728
Home.getInitialProps = ({ store }: any) => {
2829
console.log(store)
29-
store.dispatch.home.SET_PLAYERS([100, 200, 300])
30+
// TODO
31+
// 在这个生命周期调用getInitialData
32+
store.dispatch.home.getInitialData()
33+
// store.dispatch.home.SET_PLAYERS([100, 200, 300,999])
3034
}
3135

3236
const mapStateToProps = (state: any) => ({
33-
home: state.home,
34-
});
37+
home: state.home
38+
})
3539

3640
const mapDispatchToProps = (dispatch: any) => ({
41+
getInitialData:(dispatch:any)=>{
42+
dispatch.home.getInitialData()
43+
}
44+
})
3745

38-
});
39-
40-
export default isConnect({
41-
css,
42-
mapStateToProps,
43-
mapDispatchToProps
44-
}, Home)
46+
export default isConnect(
47+
{
48+
css,
49+
mapStateToProps,
50+
mapDispatchToProps
51+
},
52+
Home
53+
)

src/client/store/home/index.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,27 @@ export default createModel<any>()({
1717
}
1818
},
1919
effects: (dispatch: any) => {
20-
const { players } = dispatch
20+
const { home } = dispatch
2121
return {
2222
async getPlayers(): Promise<any> {
2323
let response = await fetch('https://www.balldontlie.io/api/v1/players')
2424
let { data }: { data: HomeState[] } = await response.json()
25-
players.SET_PLAYERS(data)
25+
home.SET_PLAYERS(data)
26+
},
27+
// ! 约定的方法
28+
getInitialData(){
29+
return new Promise(resolve=>{
30+
//延迟 500ms 返回数据
31+
setTimeout(() => {
32+
const data = {
33+
test:'123'
34+
}
35+
resolve(data);
36+
//更新状态
37+
console.log('players',home)
38+
home.SET_PLAYERS([800,800,800])
39+
}, 500);
40+
})
2641
}
2742
}
2843
}

src/client/store/reducers.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import { init } from '@rematch/core'
22
import promise from 'redux-promise-middleware'
33
import home from './home'
44

5-
const store = init({
6-
models: { home },
7-
redux: {
8-
reducers: {},
9-
middlewares: [promise]
10-
}
11-
})
125

13-
export default store
6+
export default function getStore(initState?:any){
7+
const store = init({
8+
models: { home },
9+
redux: {
10+
initialState:initState,
11+
reducers: {},
12+
middlewares: [promise]
13+
}
14+
})
15+
return store
16+
}

0 commit comments

Comments
 (0)