const Home: ModelConfig= { state: { count: 1 }, reducers: { increment(state, payload) { return { count: payload }; } }, effects: { async incrementAsync(payload, rootState) { await new Promise((resolve) => setTimeout(resolve, 1000)); this.increment(payload); } } }; export default Home;
然后通过根 store 中进行 init。
import { init } from '@rematch/core'; import models from './models'; const store = init({ models: {...models} }); export default store;
然后可以绑定在我们 redux 的 Provider 中。
<Provider store = {store}> <Router> <AppRoutes context={context} initialData={this.initialData} /> </Router> </Provider>
路由方面我们需要把组件的 init 方法绑定在路由上方便服务端请求数据时使用。
<Switch> { routes.map((d: any) => ( <Route<InitRoute> key={d.path} exact={d.exact} path={d.path} init={d.init || ''} component={d.component} /> )) } <Route path='https://www.jb51.net/' component={Home} /> </Switch>
以上就是客户端需要进行的操作了,因为 SSR 中我们服务端也需要进行数据的操作,所以为了解耦,我们就新建另一个 ServiceStore 来提供服务端使用。
在服务端构建 Html 前,我们必须先执行完当前组件的 init 方法。
import { matchRoutes } from 'react-router-config'; // 用matchRoutes方法获取匹配到的路由对应的组件数组 const matchedRoutes = matchRoutes(routes, url); const promises = []; for (const item of matchedRoutes) { if (item.route.init) { const promise = new Promise((resolve, reject) => { item.route.init(serverStore).then(resolve).catch(resolve); }); promises.push(promise); } } return Promise.all(promises);
注意我们新建一个 Promise 的数组来放置 init 方法,因为一个页面可能是由多个组件组成的,我们必须等待所有的 init 执行完毕后才执行相应的 html 构建。
现在可以得到的数据挂在 window 下,等待客户端的读取了。
let ret = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"> </head> <body> <div>${html}</div> <script type="text/javascript">window.__INITIAL_STORE__ = ${JSON.stringify( extra.initialStore || {} )}</script> </body> </html> `;
然后在我们的客户端中读取刚刚的 initialStore 数据
.... const defaultStore = window.__INITIAL_STORE__ || {}; const store = init({ models, redux: { initialState: defaultStore } }); export default store;
至此,redux的同构基本完成了,因为边幅的限定,我就没有贴太多代码,大家可以到文章底部的点击我的仓库看看具体代码哈,然后我再说说几个 redux 同构中比较坑的地方。
1.使用不了 @loadable/component 异步组件加载,因为不能获取组件内部方法。 解决的办法就是在预请求我们不放在组件中,直接拆分出来写在一个文件中统一管理,但我嫌这样不好管理就放弃了异步加载组件了。
2.在客户端渲染的时候如果数据一闪而过,那就是初始化数据并没有成功,当时这里卡了我好久喔。
css样式直出
首先,服务端渲染的时候,解析 css 文件,不能使用 style-loader 了,要使用 isomorphic-style-loader 。使用 style-loader 的时候会有一闪而过的现象,是因为浏览器是需要加载完 css 才能把样式加上。为了解决这样的问题,我们可以通过isomorphic-style-loader 在组件加载的时候把 css 放置在全局的 context 里面,然后在服务端渲染时候提取出来,插入到返回的HTML中的 style 标签。
组件的改造
import withStyles from 'isomorphic-style-loader/withStyles'; @withStyles(style) class Home extends React.Component { ... render() { const {count}:any = this.props; return ( ... ); } } const mapStateToProps = (state:any) => { return { count: state.Home.count }; }; const mapDispatchToProps = (dispatch:any) => ({ incrementAsync: dispatch.Home.incrementAsync }); export default connect( mapStateToProps, mapDispatchToProps )(Home);
withStyle 是一个柯里化函数,返回的是一个新的组件,并不影响 connect 函数,当然你也可以像 connect 一样的写法。withStyle 主要是为了把 style 插入到全局的 context 里面。
根组件的修改
import StyleContext from 'isomorphic-style-loader/StyleContext';