前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >react 同构初步(2)

react 同构初步(2)

作者头像
一粒小麦
发布2019-12-19 14:43:53
2K2
发布2019-12-19 14:43:53
举报
文章被收录于专栏:一Li小麦一Li小麦
这是一个即时短课程的系列笔记。建

命令行合并工具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管理。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 命令行合并工具concurrently
  • ssr路由渲染
  • ssr支持redux
    • 定义store和mock数据
      • 应用redux
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档