koa react 构建

2019-06-19 17:49:57 weixin_45275260 阅读数 232

前言

此项目是个人博客,有前端界面+后台管理系统;目的是当做react和node的练手项目,同时还可以了解到服务器nginx部署web站点以及备案和域名的基本操作流程。


项目预览地址

https://www.lxsblog.cn


GitHub地址

https://github.com/LinWeb/blog


体验地址


界面截图

  • 博客主页
    image.png

  • 后台系统主页
    image.png


技术栈

前端技术栈

react + antd + react-router + react-redux + axios

后端技术栈

koa2 + koa-router + mysql + sequelize


项目结构详解

后端项目结构

├── app                             //前端项目
├── config                          //项目配置
│   ├── db.js                       //数据库配置文件
│   └── index.js                    //token的key名
├── controller                      //控制器
├── lib                             //工具
│   ├── bcrypt.js                   //密码加密
│   └── token.js                    //登录token
├── middlewares                     //中间件
│   ├── checkAuth.js                //检查是普通用户还是管理员
│   ├── checkToken.js               //检查是否已经登录用户
│   └── errorHandler.js             //错误中间件
├── model                           //数据表模型
├── router                          //路由配置
├── app.js                          //项目入口
└── README.md                       //项目说明

前端项目结构

└── src                             //项目主体
    ├── assets                      //资源文件夹
    ├── component                   //组件库
    │   ├── web                     //前台界面组件库
    │   ├── common                  //公共组件库
    │   └── admin                   //后台管理系统组件库
    ├── config                      //配置文件夹
    │   ├── api.js                  //接口配置文件
    │   └── routerConfig.js         //路由配置
    ├── lib                         //放置插件和工具
    │   ├── axios.js                //axios全局配置
    │   ├── checkAuth.js            //检查是否是普通用户还是管理员
    │   └── marked.js               //markdown转换器
    ├── router                      //路由
    │   ├── admin.js                //后台路由
    │   ├── index.js                //路由入口
    │   ├── main.js                 //路由中间件
    │   └── web.js                  //前台路由
    ├── service                     //接口请求
    ├── store                       //数据仓库
    ├── view                        //前台页面
    │   ├── web                     //前台界面页面
    │   ├── common                  //公共页面
    │   └── admin                   //后台管理系统页面
    └── index.js                    //项目入口

项目运行

  • 配置数据库

安装mysql,新建一个名称叫“blog”的数据库

  • 安装依赖

项目根目录和根目录的app目录下分别安装

npm install
  • 启动项目

项目根目录和根目录的app目录下分别启动

npm start

后台模块

  • 文章
    • 文章列表
    • 文章详情
    • 标签筛选
    • 分类筛选
    • 新增文章
    • 删除文章
    • 更新文章
  • 标签
    • 标签列表
  • 分类
    • 分类列表
  • 评论
    • 评论列表
    • 新增评论
    • 删除评论
  • 回复
    • 新增回复
    • 删除回复
  • 用户
    • 注册用户
    • 用户登录
    • 用户列表
    • 删除用户
    • 更新用户
    • 校验用户名是否已注册过

项目优化

目前,已优化了一部分,首次加载需要3,4秒的时间,由于目前租的云服务器的宽带只有1M。
到现在做的优化有以下几点:

  • antd做按需加载
  • highlight.js高亮依赖包只引入需要的模块
  • 前端webpack+nginx 使用GZIP压缩,不采用koa后端压缩,减少I/O的消耗
  • webpack配置splitChunks切割模块代码,生成多个模块js文件
  • 采用自定义高阶组件bundle.js+import(’’),按需动态加载组件

持续优化中~


项目总结

  • 开始做这个项目的时候,先明确项目的需求,然后设计好数据表,考虑到数据表的联系
  • 因为项目是使用sequelize操作mysql,所以要提前了解开发文档,不然会走很多坑
  • 个人来说,使用react的一个缺点是对数据监听方面没有vue用起来顺畅
  • 使用antd这个react组件库,带来了界面设计方面的便利,自己只是再做一些样式的优化即可
2019-04-01 03:03:42 weixin_34007906 阅读数 80

前言

由于一直在用 vue 写业务,为了熟悉下 react 开发模式,所以选择了 react。数据库一开始用的是 mongodb,后来换成 mysql 了,一套下来感觉 mysql 也挺好上手的。react-router、koa、mysql 都是从0开始接触开发的,期间遇到过很多问题,印象最深的是 react-router 参考官方文档配置的,楞是跑不起来,花费了好几个小时,最后才发现看的文档是v1.0, 而项目中是v4.3, 好在可参考的资料比较多,问题都迎刃而解了。

博客介绍

  • 前端项目通过 create-react-app 构建,server端通过 koa-generator 构建
  • 前后端分离,博客页、后台管理都在 blog-admin 里,对含有 /admin 的路由进行登录拦截
  • 前端: react + antd + react-router4 + axios
  • server端: koa2 + mysql + sequelize
  • 部署:server端 运行在 3000 端口,前端 80 端口,nginx设置代理

预览地址

web端源码

server端源码

喜欢或对你有帮助,欢迎 star

功能

  • 登录
  • 分页
  • 查询
  • 标签列表
  • 分类列表
  • 收藏列表
  • 文章列表
  • 发布文章时间轴
  • 文章访问次数统计
  • 回到顶部
  • 博客适配移动端
  • 后台适配移动端
  • 对文章访问次数进行可视化
  • 留言评论
  • 渲染优化、打包优化

效果

标签

分类

收藏

文章

编辑

博客页

响应式

运行项目

前端

git clone https://github.com/gzwgq222/blog-admin.git
cd blog-admin
npm install
复制代码

localhost:2019

server 端

本地安装 mysql,新建 dev 数据库

git clone https://github.com/gzwgq222/blog-server.git
cd blog-server
npm install
复制代码

server 端

前端 react + antd 开发,较为平缓,在此就不再叙述。主要记录下 koa + mysql 相关事宜

全局安装 koa-generator

npm install -g koa-generato
复制代码

创建 node-server 项目

koa node-server 
复制代码

安装依赖

cd node-server 
npn install
复制代码

运行

npm dev
复制代码

出现 Hello Koa 2! 表示运行成功

先看routes文件

index.js

const router = require('koa-router')()
router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})
router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})
router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})
module.exports = router
复制代码

users.js

const router = require('koa-router')()
router.prefix('/users')
router.get('/', function (ctx, next) {
  ctx.body = 'this is a users response!'
})
router.get('/bar', function (ctx, next) {
  ctx.body = 'this is a users/bar response'
})
module.exports = router
复制代码

分别访问下列路由

localhost:3000/string localhost:3000/users localhost:3000/bar

大概你已经猜到了,koa-router 定义路由访问时返回相应的内容,那我们只需要把相应的 data 返回去就行了,只是我们的数据得从数据库查询出来。

本地安装 mysql

项目安裝 mysql

npm install mysql --save
复制代码

项目安裝 sequelize

sequelize 是 ORM node框架,对SQL查询语句的封装,让我们可以用OOP的方式操作数据库

npm install --save sequelize
复制代码

新建 sequelize.js,建立连接池

const Sequelize = require('sequelize');
const sequelize = new Sequelize('dev', 'root', '123456', {
  host: 'localhost',
  dialect: 'mysql',
  operatorsAliases: false,
  pool: {
    max: 5,
    min: 0,
    acquire: 30000,
    idle: 10000
  }
})
sequelize
  .authenticate()
  .then(() => {
    console.log('MYSQL 连接成功......');
  })
  .catch(err => {
    console.error('链接失败:', err);
  });
// 根据模型自动创建表
sequelize.sync()
module.exports = sequelize
复制代码

创建 model、controllers 文件夹 定义model:定义表结构;controller:定义对数据库的查询方法

以 tag.js 为例

model => tag.js

const sequelize = require('../sequelize ')
const Sequelize = require('sequelize')
const moment = require('moment') // 日期处理库
// 定义表结构
const tag = sequelize.define('tag', { 
  id: {
    type: Sequelize.INTEGER(11), // 设置字段类型
    primaryKey: true, // 设置为主建
    autoIncrement: true // 自增
  },
  name: {
    type: Sequelize.STRING,
    unique: { // 唯一
      msg: '已添加'
    }
  },
  createdAt: {
    type: Sequelize.DATE,
    defaultValue: Sequelize.NOW,
    get() {
      // this.getDataValue 获取当前字段value
      return moment(this.getDataValue('createdAt')).format('YYYY-MM-DD HH:mm')
    }
  },
  updatedAt: {
    type: Sequelize.DATE,
    defaultValue: Sequelize.NOW,
    get() {
      return moment(this.getDataValue('updatedAt')).format('YYYY-MM-DD HH:mm')
    }
  }
},
{
  // sequelize会自动使用传入的模型名(define的第一个参数)的复数做为表名 设置true取消默认设置
  freezeTableName: true
})
module.exports = tag
复制代码

controller => tag.s 定义了 create、findAll、findAndCountAll、destroy 方法

const Tag = require('../model/tag')
const Op = require('sequelize').Op
const listAll = async (ctx) => {
  const data = await Tag.findAll()
  ctx.body = {
    code: 1000,
    data
  }
}
const list = async (ctx) => {
  const query = ctx.query
  const where = {
    name: {
      [Op.like]: `%${query.name}%`
    }
  }
  const {rows:data, count: total } = await Tag.findAndCountAll({
    where,
    offset: (+query.pageNo - 1) * +query.pageSize,
    limit: +query.pageSize,
    order: [
      ['createdAt', 'DESC']
    ]
  })
  ctx.body = {
    data,
    total,
    code: 1000,
    desc: 'success'
  }
}
const create = async (ctx) => {
  const params = ctx.request.body
  if (!params.name) {
    ctx.body = {
      code: 1003,
      desc: '标签不能为空'
    }
    return false
  }
  try {
    await Tag.create(params)
    ctx.body = {
      code: 1000,
      data: '创建成功'
    }
  }
  catch(err) {
    const msg = err.errors[0]
    ctx.body = {
      code: 300,
      data: msg.value + msg.message
    }
  }
}
const destroy = async ctx => {
  await Tag.destroy({where: ctx.request.body})
  ctx.body = {
    code: 1000,
    desc: '删除成功'
  }
}
module.exports = {
  list,
  create,
  listAll,
  destroy

复制代码

在 routers 文件夹 index.js 中引入定义好的 tag controller ,定义路由

const router = require('koa-router')()
const Tag = require('../controllers/tag')
// tag
router.get('/tag/list', Tag.list)
router.get('/tag/list/all', Tag.listAll)
router.post('/tag/create', Tag.create)
router.post('/tag/destroy', Tag.destroy)
module.exports = router
/* 如每个 route 是单独的文件,可以使用 router.prefix 定义路由前缀
router.prefix('/tag')
router.get('/list', Tag.list)
router.get('/list/all', Tag.listAll)
router.post('/create', Tag.create)
router.post('/destroy', Tag.destroy)
*/
复制代码

因为 app 中 已经引入 routers 中的 index.js 调用了 app.use了,所以此处不需再引入 在浏览器里输入 localhost:3000/tag/list 就可以看到返回的数据结构了,只不过 data 为空数组,因为我们还没添加进去任何数据 到这里,model 定义表结构、sequelize操作数据库、koa-router 定义路由 这一套流程算是完成了,其他表结构,接口 都是一样定义的

总结

之前没有写过 node server 和 react,算是从零搭建该博客,踩了一些坑,也学到了很多东西,譬如react 开发模式、react-router、sequelize 操作mysql的crud、koa、nginx的配置等等。

麻雀虽小,也是一次完整的前后端开发体验,脱离了浏览器的限制,像海贼王一样,打开了新世界的大门,寻找 onepiece ......

web端源码

server端源码

详细的 server 端说明

后续会在个人博客中添加关于此次部署文章

Links

初尝 react + Node,错误之处还望斧正,欢迎提 issue

2018-01-21 11:35:21 weixin_34198881 阅读数 218

本项目github地址 react-koa2-ssr

所用到技术栈 react16.x + react-router4.x + koa2.x

前言

前段时间业余做了一个简单的古文网 ,但是项目是使用React SPA 渲染的,不利于SEO,便有了服务端渲染这个需求。后面就想写个demo把整个过程总结一下,同时也加深自己对其的理解,期间由于工作,过程是断断续续 。总之后来就有了这个项目吧。关于服务端渲染的优缺点,vue服务端渲染官方文档讲的最清楚。讲的最清楚。 对于大部分场景最主要还是两点 提高首屏加载速度 和方便SEO.为了快速构建开发环境,这里直接使用create-react-app 和koa2.x生成一个基础项目 。整个项目便是以此作为基点进行开发的,目前也只是完成了最基本的需求, 还有很多Bug 和可以优化的地方, 欢迎交流。

服务端渲染最基本的理论知识梳理

首先前后端分别使用create-react-app 和koa2的脚手架快速生成, 然后再将两个项目合并到一起。这样我们省去了webpack的一些繁琐配置 ,同时服务端使用了babel编译。看这个之前 默认已经掌握webpack 和 koa2.x,babel的相关知识。 我们直切重要的步骤吧。我觉得搭建一个react-ssr环境主要只有三点 第一是react服务端提供的渲染API,二是前后端路由的同构,三则是初始化异步数据的同构。因此这个简单的demo主要从这三方面入手。

  • react 服务端渲染的条件
  • react-router4.x 与koa2.x 路由实现同构
  • redux 初始数据同构

react 服务端渲染的条件

其实可以看 《深入React技术栈》的第七章, 介绍的非常详细。 概括来说 React 之所以可以做到服务端渲染 是因为ReactDOM提供了服务端渲染的API

  • renderToString  把一个react 元素转换成带reactid的html字符串。
  • renderToStaticMarkup 转换成不带reactid的html字符串,如果是静态文本,用这个方法会减少大批的reactid. 这两个方法的存在 ,实际上可以把react看做是一个模板引擎。解析jsx语法变成普通的html字符串。

我们可以调用这两个API 实现传入ReactComponent 返回对应的html字符串到客户端。浏览器端接收到这段html以后不会重新去渲染DOM树,只是去做事件绑定等操作。这样就提高了首屏加载的性能。

react-router4.x 和 服务端的路由实现同构。

react-router4.x 相对于之前的版本,做了较大的改动。 整个路由变得组件化了。 可以着重看这里 官方给出了详细的例子和文档可以作为基本思想的和标准参考。

服务端渲染与客户端渲染的不同之处在于其路由是没有状态的,所以我们需要通过一个无状态的router组件 来包裹APP,通过服务端请求的url来匹配到具体的路由数组和其相关属性。 所以我们在客户端使用 BrowserRouter,服务端则使用无状态的 StaticRouter。

  • BrowserRouter 使用 HTML5 提供的 history API (pushState, replaceState 和 popstate 事件) 来保持 UI 和 URL 的同步。
  • StaticRouter 是一个不会改变地址的router组件 。 参考代码如下所示:
// 服务端路由配置
import { createServer } from 'http'
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { StaticRouter } from 'react-router'
import App from './App'

createServer((req, res) => {
  const context = {}

  const html = ReactDOMServer.renderToString(
    <StaticRouter
      location={req.url}
      context={context}
    >
      <App/>
    </StaticRouter>
  )

  if (context.url) {
    res.writeHead(301, {
      Location: context.url
    })
    res.end()
  } else {
    res.write(`
      <!doctype html>
      <div id="app">${html}</div>
    `)
    res.end()
  }
}).listen(3000)
And then the client:import ReactDOM from 'react-dom'

// 客户端路由配置
import { BrowserRouter } from 'react-router-dom'
import App from './App'

ReactDOM.render((
  <BrowserRouter>
    <App/>
  </BrowserRouter>
), document.getElementById('app'))

复制代码

我们把koa的路由url传入 ,后者会根据url 自动匹配对应的React组件,这样我们就能实现,刷新页面,服务端返回的对应路由组件与客户端一致。 到这一步我们已经可以实现页面刷新 服务端和客户端保持一致了。

Redux 服务端同构

首先下官方文档做了简单的介绍介绍cn.redux.js.org/docs/recipe…

其处理步骤如下:

  • 1 我们根据对应的服务端请求API 得到对应的异步方法获取到异步数据。
  • 2 使用异步数据生成一个初始化的store const store = createStore(counterApp, preloadedState),
  • 3 然后调用const finalState = store.getState()方法获取到store的初始化state.
  • 4 将初始的initState 作为参数传递到客户端
  • 5 客户端初始化的时候回去判断 window.INITIAL_STATE 下面是否有数据,如果有则作为初始数据重新生成一个客户端的store. 如下面代码所示。

服务端

 <html>
      <head>
        <title>Redux Universal Example</title>
      </head>
      <body>
        <div id="root">${html}</div>
        <script>
          window.__INITIAL_STATE__ = ${JSON.stringify(finalState)}
        </script>
        <script src="/static/bundle.js"></script>
      </body>
    </html>    
复制代码

客户端

...
// 通过服务端注入的全局变量得到初始 state
const preloadedState = window.__INITIAL_STATE__

// 使用初始 state 创建 Redux store
const store = createStore(counterApp, preloadedState)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
复制代码

这个基本上就是一个标准的redux同构流程, 其实更多的官方是在给我们提供一种标准化的思路,我们可以顺着这个做更多的优化。 首先我们并不需要直接通过API作为映射 服务端和客户端各搞一套异步加载的方法,这样显得非常冗余。 react-router 包里面提供了react-router-config主要用于静态路由配置。 提供的 matchRoutes API可以根据传入的url 返回对应的路由数组。我们可以通过这个方法在服务端直接访问到对应的React组件。 如果要从路由中直接获取异步方法,我看了很多类似的同构方案,

  • 主要有两种方式一种是直接在路由中增加一个thunk方法,通过这个方法直接去获取初始化的异步数据, 我觉得优点是比较明确直观,直接在路由层就把这个事情解决了。
  • 第二种是利用class 的静态方法。我们可以通过路由访问到组件的类下面的static方法。 这样我们就直接可以在容器组件内部同时声明服务端初始化方法和客户端初始化方法了 这样处理的层级放到了组件里面我自己觉得更能体现组件的独立性吧。

本项目采用了第二种方案,先看一下代码:


/**
 * 渲染服务端路由
 */
module.exports.render = async(ctx,next) =>{
    const { store ,history} = getCreateStore(ctx);
    const branch = matchRoutes(router, ctx.req.url);
    const promises = branch.map(({route}) => {
        const fetch = route.component.fetch;
        return fetch instanceof Function ? fetch(store) : Promise.resolve(null)
    });
    await Promise.all(promises).catch((err)=>{
        console.log(err);
    }); 

    const html = ReactDOMServer.renderToString(
                <Provider store={store}>
                            <StaticRouter
                            location={ctx.url}
                            context={{}}>
                                <App/>
                            </StaticRouter>
                </Provider>
        )
        let initState=store.getState();
        const body =  layout(html,initState);
   ctx.body =body;
}

复制代码

对应容器组件提供了一个静态的fetch方法

class Home extends Component {
  ...
  static fetch(store){
        return store.dispatch(fetchBookList({page:1,size:20}))
  }

复制代码

这是我们的 actions

/**
 * 获取书籍目录
 * @param {*} param 
 */
export const fetchBookList = (params) => {
    return async (dispatch, getState) => {
        await axios.get(api.url.booklist, {
            params: params
        }).then((res) => {
            dispatch(booklist(res.data.result));
        }).catch((err) => {

        })
    }
}
复制代码

首先我们通过 matchRoutes 拿到当前路由下所有的路由,再对其遍历得到有关一个异步方法的Promise数组,这里我们所谓的异步方法就是actions中的异步方法。由于我们在服务端也初始化的store所以我们可以直接在服务端调用actions,这里我们需要给容器组件的static方法传入store ,这样我们就可以通过store.dispatch(fetchBookList({page:1,size:20}))调用actions了。上面的方法我们得到了一个Promise 数组。我们使用 Promise.all将异步全部执行。这个时候实际上 store的运行跟客户端是一样的。 我们在异步的过程中 将初始数据全部写入了 store中。所以我们通过store.getState()就可以拿到初始化数据了。客户端的初始化跟Redux官方例子是一样的。直接判断是否传入初始化state,如果传入就做为初始化数据。我们服务端的初始化异步和客户端的初始化异步 如何避免重复。 这里我们直接先获取store中的对应初始数据 ,看是否存在,如果不存在我们再进行加载。

到这一步我们已经可以实现刷新页面异步数据服务端处理,不刷新页面前端处理,一个基本的同构方案主体就出来了,剩下的就是一些优化项和一些项目定制性的东西了。

服务端页面分发

对于服务器而言不仅会收到前端路由的请求还会收到各种其他静态资源的请求 import {matchPath} from 'react-router-dom'; 我们这里使用react-router-dom包里面的 matchPath API 来匹配当前请求路由是否与我们客户端的路由配置相同如果不同我们默认为请求的是静态资源或其他。如果不匹配当前路由我们直接执行 next() 进入到下一个中间件 。因为我们这个项目实际上还是是一个前后端分离的项目 只不过增加了服务端渲染的方式而已。 如果服务端还要处理其他请求,那么其实我们也可以在通过服务端 增加其他路由 ,通过映射来匹配对应的渲染页面和API。

其他

写这个demo看了很多的github项目以及相关文章,这些资料对本项目有很大的启发

Vue.js 服务器端渲染指南

react-server

beidou

react-ssr-optimization

React-universal-ssr

fairy

D2 - 打造高可靠与高性能的React同构解决方案

Egg + React 服务端渲染开发指南

服务端渲染与 Universal React App

React同构直出优化总结

React移动web极致优化

github.com/joeyguo ...

总结

我们知道服务端渲染的 优势在于可以极快的首屏优化 ,支持SEO,与传统的SPA相比多了一种数据的处理方式。 缺点也非常明显,服务端渲染相当于是把客户端的处理流程部分移植到了服务端,这样就增加了服务端的负载。因此要做一个好的SSR方案,缓存是必不可少的。与此同时工程化方面也是有很多值得优化的地方。这里只是浅尝辄止,并没有做相关的处理,估计后面有时间会做一些优化欢迎大家关注。

本项目github地址 github.com/yangfan0095…

以上です

2019-08-01 14:57:20 qq_37860930 阅读数 680

前言

自己一直想做个包括前后端的项目,了解和熟悉一个网站从构建到部署整个流程。正好以前做过一个react的后台模板(纯前端)。另外在慕课网找了一个node项目学习后,开始自己做一个前后台项目。

所有权限都在后台做,比如非管理员不能添加和删除作品,如果仅靠前台禁用按钮,用户可以直在浏览器修改dom属性绕过禁用功能,所以前台只是起对不同权限用户展示不同UI的功能,而不是通过前台做权限功能。

想测试多个账号在线,请用不同的浏览器登陆

项目地址github地址
预览地址预览地址

1.项目截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2.项目功能

  • 用户注册功能
  • 用户登录功能
  • 修改账号信息上传头像等功能
  • 网站换肤、切换全屏功能
  • 网站留言板
  • 聊天室

jwt实现注册与登陆系统
react+koa实现留言板功能
webSocket实现聊天室功能
react编写打字组件
antd实现换肤功能
富文本编辑器braft-editor的使用


3.前端

前端使用的技术栈:react、react-router、redux、canvas、fetch、antd、websocket、es6

3.1创建

使用create-react-app脚手架搭建了前端项目并扩展了webpack的配置(配置可以参考这里)。
新增修饰器语法,这是ES7的一个新语法,作用就是修改组件的一些属性。比如路由的withRouter将组件包裹,可以在组件的props上传递history对象等等。

//不使用修饰器语法
withRouter(Test)

//使用修饰器语法
@withRouter
class Test extends React.Component{
	...
}

当有一个组件需要多个高阶组件包裹时,这种写法就很有优势

withRouter(Form.create()(Test))

@withRouter @Form.create() ...
class Test extends React.Component{
	...
}

当然包裹的顺序也很重要


3.2依赖

自己根据需求封装了一个fetch来进行前后台通信,使用简单。

const res = await get('/test')
console.log(res)
3.3组件

公用组件应该完全隔离业务逻辑,我通过事件分发和props传递编写了以下组件,这些组件完全隔离了业务逻辑,所以在其它项目中也可以使用。

组件 组件的作用 组件接收的props
AnimatedBooks 展示翻书动画效果的组件 content(内容) 、cover(封面)
Background 登录粒子背景 url(背景图)
ColorPicker 拾色器 color(当前颜色)、onChange(颜色改变的函数)
Loading loading动画
Typing 打字插件 delay(打印延时)、frequency(打印频率)、done(打印完成的回调)
3.4页面

页面可以拆分成不同的组件,然后将组件拼接成一个页面,使得页面维护和编写变得简单。但是会带来不同组件之间通信的问题,解决的方法有两个:

  • 状态提升
  • 状态管理

状态提升适用于组件关系简单的通信,比如兄弟组件之间的通信,就可以将状态提升到它们的父组件中,通过props和事件传递给子组件。这种方式不适合层级结构太深的组件通信,否则一层层传递太过麻烦。

在Index页面中,我将页面按照UI拆分成了头部、侧边栏、中间部分、底部组件,然后使用了状态提升进行组件之间的通信。

状态管理适用于需要全局使用的状态或者组件嵌套复杂的状态,

至于如何拆分页面,我认为可以按UI拆分功能拆分,如果拆分的组件可以共用,我们可以进一步抽象成公共组件使用(公共组件就是将业务逻辑解耦出去)


4.项目后端

后端采用的是koa、mysql使用了一些koa的中间件

4.1创建

后端通过koa-generator脚手架搭建后端项目
这是我第一次写后端项目,对于后端的的mvc也不算特别熟悉,参考网上的介绍后以自己的理解去写了,routes文件存储路由、controller文件件实现对应的路由逻辑,如果有问题欢迎指出。

为什么要使用框架?
原生的node只提供了最基本的功能,我们还需要编写一些额外的代码才能达到我们的要求,比如获取前台传过来的参数,原生node只提供了url属性,需要我们自己解析url来获取querying,而koa2框架已经封装好了,直接在ctx中提供了query对象;还有前台post的数据、cookie、session、路由等,在原生中都需要我们自己处理,而koa2框架直接封装好了,就算koa2没有提供也有很多其他人编写好了的中间件供我们使用。

我认为使用的框架的意义就是编写更好可维护的代码,减少和业务逻辑无关的代码,比如解析前台参数、数据库连接等等重复的代码。

之所以用koa2而不用express是相较于promise我更喜欢async/await。

接口规范

当然也有很多方式定义状态码,只有自己写好文档即可。

{
	"status":0,     //0代表成功,1失败
	"httpCode":200,    
	"message":"请求成功",
	"data":{}
}

5.项目部署

5.1项目打包

我将前端项目打包到node服务器上,
我想访问网址根目录就能直接访问项目首页了

//app.js
app.use(views(__dirname + '/public/build'))   //这里的路径是前端项目打包后放在后端的位置

//routes ->index.js
router.get('/', async (ctx, next) => {
	await ctx.render('index.html')
})

由于我们前端使用的是HTML5的history,页面一刷新就404了,所以后端要配置。我使用了koa-connect-history-api-fallback中间件来支持。
中间件实现的功能是如果 当URL 匹配不到任何静态资源,返回指定的页面(中间件默认返回的是index.html,配置参考文档

//app.js
const historyApiFallback = require('koa-history-api-fallback')
app.use(historyApiFallback());

这里要注意中间的顺序,historyApiFallback一定要放在所有接口路由后面,否则所有接口都是返回index.html了。
historyApiFallback一定要在静态资源前面,否则资源找不到

//app.js
const historyApiFallback = require('koa-history-api-fallback')
// routes
app.use(index.routes(), index.allowedMethods())
app.use(user.routes(), user.allowedMethods())
app.use(works.routes(), works.allowedMethods())
app.use(message.routes(), message.allowedMethods())
app.use(score.routes(), score.allowedMethods())

app.use(historyApiFallback());

app.use(require('koa-static')(__dirname))
app.use(require('koa-static')(__dirname + '/public/build'))
app.use(require('koa-static')(__dirname + '/public/upload-files'))

另外项目用了jwt进行权限认证了,前端打包后的资源(css、js、图片等)这些资源不应该权限认证,否则401,前端获取不到这些js来生成页面了

上线时,创建数据报错Unknown collation: 'utf8mb4_0900_ai_ci',我的数据库版本不支持这种格式。SHOW COLLATION,可查看数据库所有支持的collation,我将排序规则改为utf8mb4_general_ci


总结

期间遇见的问题我都是边找边学边用,磕磕绊绊完成了这个小项目。通过这个小项目,我熟悉了一个基本的网站从零到上线的整个流程,并从中发现问题、解决问题。这也是我写这个项目的初衷。我并没有过多深入每个细节,如数据库的字段设计,加密、防止mysql的注入、数据库优化等。

部署时,发现网页太慢了,查看network发现
用了nginx的gzip进行了压缩。
压缩前
在这里插入图片描述
压缩后
在这里插入图片描述
可以看到size明显变小了,但是请求的时间还是有点长
下面是我的nginx配置

upstream admin{                                                                                                            
    server 47.99.130.140:8888;                                                                                             
}                                                                                                                          
upstream reactMusic{                                                                                                       
    server 47.99.130.140:8088;                                                                                             
}                                                                                                                          
                                                                                                                           
                                                                                                                           
                                                                                                                           
server{                                                                                                                    
     gzip on;                                                                                                              
     gzip_min_length 1k;                                                                                                   
     gzip_buffers 4 16k;                                                                                                   
     gzip_comp_level 6;                                                                                                    
     #gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;     
     gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/x
ml application/xml+rss text/javascript application/x-font-woff;                                                            
     gzip_http_version 1.1;                                                                                                
     gzip_vary on;                                                                                                         
     gzip_proxied off;                                                                                                     
                                                                                                                           
     listen 80;                                                                                                            
     server_name 47.99.130.140;                                                                                            
     location /admin/ {                                                                                                    
          proxy_redirect   off;                                                                                            
            proxy_set_header Host $host;                                                                                   
            proxy_set_header X-Forwarded-For $remote_addr;                                                                 
            proxy_set_header X-Real-IP $remote_addr;                                                                       
            proxy_pass http://admin/;                                                                                      
     }                                                                                                                     
     location /react-music/ {
                 proxy_set_header Host $host;                                                                              
            
            proxy_set_header X-Forwarded-For $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
                proxy_pass http://reactMusic/;   
     }

}                                                                                                        
                                                                           
                                                                           

2018-10-30 15:53:25 qq_20069429 阅读数 827

原地址

https://github.com/yangfan0095/react-koa2-ssr

启动

npm install

客户端运行

npm run start

服务端运行

先执行客户端打包在运行server (个人推荐使用vscode作为服务端开发工具)

npm run build

npm run server

前言

前段时间做了一个简单的古文网 ,但是项目是使用React SPA 渲染的,不利于SEO,便有了服务端渲染这个需求。后面就想写个demo把整个过程总结一下,同时也加深自己对其的理解,期间由于工作,过程是断断续续 。总之后来就有了这个项目吧。关于服务端渲染的优缺点,vue服务端渲染官方文档讲的最清楚。 对于大部分场景最主要还是两点 提高首屏加载速度 和方便SEO.为了快速构建开发环境,这里直接使用create-react-app 和koa2.x生成一个基础项目 。整个项目便是以此作为基点进行开发的,目前也只是完成了最基本的需求, 还有很多Bug 和可以优化的地方, 欢迎交流。

服务端渲染最基本的理论知识梳理

首先前后端分别使用create-react-app 和koa2的脚手架快速生成, 然后再将两个项目合并到一起。这样我们省去了webpack的一些繁琐配置 ,同时服务端使用了babel编译。看这个之前 默认已经掌握webpack 和 koa2.x,babel的相关知识。 我们直切重要的步骤吧。我觉得搭建一个react-ssr环境主要只有两点 一个是前后端路由的同构和 异步数据的同构。因此这个简单的demo主要从这两方面入手。

react 服务端渲染梳理

  • react 服务端渲染的条件
  • react-router4.x 与koa2.x 路由实现同构
  • redux 初始数据同构

react 服务端渲染的条件

其实可以看 《深入React技术栈》的第七章, 介绍的非常详细。 概括来说 React 之所以可以做到服务端渲染 是因为ReactDOM提供了服务端渲染的API

  • renderToString  把一个react 元素转换成带reactid的html字符串。
  • renderToStaticMarkup 转换成不带reactid的html字符串,如果是静态文本,用这个方法会减少大批的reactid. 这两个方法的存在 ,实际上可以把react看做是一个模板引擎。解析jsx语法变成普通的html字符串。

我们可以调用这两个API 实现传入ReactComponent 返回对应的html字符串到客户端。浏览器端接收到这段html以后不会重新去渲染DOM树,只是去做事件绑定等操作。这样就提高了首屏加载的性能。