react 同构初步(2)

2019-12-19 14:43:53 浏览数 (1)

这是一个即时短课程的系列笔记。建

命令行合并工具concurrently

现在已经有了三条指令,做项目时,必须启动三个窗口,给开发带来了不便。npm上的开源库concurrently把它们整合为一条命令,可以提升开发体验。

https://www.npmjs.com/package/concurrently

不用管太多,直接安装:

代码语言:javascript复制
npm i concurrently -S

假设我需要一条前端最熟悉的npm start来启动我们的开发,那么需要在package.json配置一条start命令:

代码语言:javascript复制
"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下创建containercomponent文件夹,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去处理。

代码语言:javascript复制
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管理。

0 人点赞