命令行合并工具concurrently
现在已经有了三条指令,做项目时,必须启动三个窗口,给开发带来了不便。npm上的开源库concurrently把它们整合为一条命令,可以提升开发体验。
https://www.npmjs.com/package/concurrently
不用管太多,直接安装:
代码语言:javascript复制npm i concurrently -S
假设我需要一条前端最熟悉的npm start
来启动我们的开发,那么需要在package.json配置一条start命令:
"start":"concurrently "npm run dev:client" "npm run dev:server" "npm run dev:start"",
安装配置完之后,就可以愉快地使用傻瓜式指令npm start了。
ssr路由渲染
在客户端,假如访问一个路由/about
,让js监听当前url变化来实现。但在服务端(node),就有很多需要注意的地方。
阅读资料,给出了最简单的方法:
https://reacttraining.com/react-router/web/guides/server-rendering
Rendering on the server is a bit different since it’s all stateless. The basic idea is that we wrap the app in a stateless <StaticRouter> instead of a <BrowserRouter>. We pass in the requested url from the server so the routes can match and a context prop we’ll discuss next.
代码语言:javascript复制// client
<BrowserRouter>
<App/>
</BrowserRouter>
// server (not the complete story)
<StaticRouter
location={req.url}
context={context}
>
<App/>
</StaticRouter>
react-router-dom开发了两套路由:StaticRouter和BrowserRouter,分别对应服务端和客户端的渲染。
在命令行安装router:
代码语言:javascript复制npm i react-router-dom -S
接下来我们对自身的代码做点改造:
(1)在src下创建container
和component
文件夹,container创建两个页面Index和About,
Index直接照搬原来的计数器(App.js)代码,About简单写一写:
代码语言:javascript复制import React from 'react';
function About(props){
return <div>
<h1>about</h1>
</div>
}
export default About;
(2)两个页面写好后,App.js改造成全局应用的入口
代码语言:javascript复制import Reactfrom 'react';
import {Route} from 'react-router-dom';
import Index from './container/Index';
import About from './container/About';
export default (
<div>
<Route exact path="/" component={Index} />
<Route exact path="/" component={About} />
</div>
);
服务端(server/index.js)怎么写呢?考虑监听一个通配符*
,然后把req.url绑定到服务端路由上,交给react的StaticRouter
去处理。
import React from 'react';
import {renderToString} from 'react-dom/server';
import express from 'express';
import {StaticRouter} from 'react-router-dom';
import App from '../src/App';
const app=express();
app.use(express.static('public'));
// 监听所有页面
app.get('*',(req,res)=>{
// react组件解析为html
const content=renderToString(
<StaticRouter location={req.url}>
{App}
</StaticRouter>
);
res.send(`
<html>
<head>
<meta charset="UTF-8">
<title>react ssr</title>
<body>
<div id="root">${content}</div>
<script src="bundle.js"></script>
</body>
</head>
</html>
`)
});
app.listen(9000,()=>{
console.log('server is runing..')
});
作为客户端(client/index.js)同构,也这样处理:
代码语言:javascript复制import React from 'react';
import ReacDom from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import App from '../src/App';
const Page=<BrowserRouter>{App}</BrowserRouter>
// 客户端
// 注水:不需render
ReacDom.hydrate(Page,document.querySelector('#root'));
处理完上述步骤,运行npm start打包。在9000端口访问:
ssr路由渲染基本功能就完成了。
ssr支持redux
store本质是数据。如果要进行同构必定包含client和server两端。
如果数据流是异步的,在client端无非就是componentDidAmount。在server端逻辑也是基本一致的。本节将就异步数据流同构的实现进行讲解。
需求:通过redux在首页渲染一个课程列表。
安装react-redux,redux,axios和redux-thunk
代码语言:javascript复制npm i react-redux axios redux redux-thunk -S
在src下创建一个store文件夹:
创建store.js
代码语言:javascript复制// 储存的入口
import { createStore, applyMiddleware, combineReducers } from "redux";
import thunk from 'thunk';
import indexReducer from './index';
const reducer = combineReducers({
index: indexReducer
});
// 创建store
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
定义store和mock数据
接下来要模拟一个接口,假设这个接口是9001端口:
在sotore文件夹下继续创建index.js,负责index首页的状态业务:
代码语言:javascript复制// 定义actionType
const GET_LIST = 'INDEX/GET_LIST';
// actionCreator
const changeList = list => ({
type: GET_LIST,
list
});
// 获取方法,假设我从9001端口获取数据
export const getIndexList = server => {
return async (dispatch, getState, axiosInstance) => {
axios.get('http://localhost:9001/course/list').then((res)=>{
const { list } = res.data;
dispatch(changeList(list));
});
}
}
// 初始状态
const defaultState = {
list: []
}
export default (state = defaultState, action) => {
switch (action.type) {
case GET_LIST:
const newState = {
...state,
list: action.list
}
return newState;
default:
return state
}
}
在项目中创建一个mock.j,作为9001的服务:
代码语言:javascript复制// 单纯模拟接口
const express=require('express');
const app=express();
app.get('/course/list',(req,res)=>{
// 支持跨域
res.header('Access-Control-Allow-Origin','*');
res.header('Access-Control-Methods','GET,POST,PUT,DELETE');
res.header('Content-Type','application/json;charset=utf-8');
res.json({
code:0,
list:[
{id:1,name:'javascript 从helloworld到放弃'},
{id:2,name:'背锅的艺术'},
{id:3,name:'撸丝程序员如何征服女测试'},
{id:4,name:'python从入门到跑路'}
]
});
});
app.listen('9001',()=>{
console.log('mock has started..');
});
注意:mock.js相对独立于此项目,也就是说,逻辑上你应该独立启动该服务。
应用redux
对页面应用redux也是分为三部分,
(1)组件应用redux
代码语言:javascript复制// 客户端
import React,{useState,useEffect} from 'react';
import {connect} from 'react-redux';
import {getIndexList} from '../store/index';
function Index(props){
const [count,setCount]=useState(1);
useEffect(()=>{
props.getIndexList();
},[]);
return <div>
<h1>react ssr</h1>
<span>{count}</span><br/>
<button onClick={()=>{setCount(count 1)}}> </button><hr/>
<ul>
{props.list.map((item,index)=>(
<li key={index}>{item.id}-{item.name}</li>
))}
</ul>
</div>
}
export default connect(
state=>({list:state.index.list}),
{getIndexList}
)(Index);
(2)客户端思路是用useEffect拿到请求方法,用provider传递状态:
代码语言:javascript复制import React from 'react';
import ReacDom from 'react-dom';
import {BrowserRouter} from 'react-router-dom';
import {Provider} from 'react-redux';
import store from '../src/store/store';
import App from '../src/App';
const Page=(<Provider store={store}>
<BrowserRouter>{App}</BrowserRouter>
</Provider>);
// 客户端
// 注水:不需render
ReacDom.hydrate(Page,document.querySelector('#root'));
(3)在服务端操作完全一样:
代码语言:javascript复制// ...
import {Provider} from 'react-redux';
import store from '../src/store/store';
import App from '../src/App';
// ...
// 监听所有页面
app.get('*',(req,res)=>{
// react组件解析为html
const content=renderToString(
<Provider store={store}>
<StaticRouter location={req.url}>
{App}
</StaticRouter>
</Provider>
);
res.send(`
<html>
<head>
<meta charset="UTF-8">
<title>react ssr</title>
<body>
<div id="root">${content}</div>
<script src="bundle.js"></script>
</body>
</head>
</html>
`)
});
打包执行:
看到此页面,我们已经通过通过ssr完成了基本的redux管理。