vue服务器渲染 vue项目改造SSR(服务端渲染)
大家好,今天小编来为大家解答以下的问题,关于vue服务器渲染,vue项目改造SSR(服务端渲染)这个很多人还不知道,现在让我们一起来看看吧!
怎样使用React服务器端渲染
这次给大家带来怎样使用React服务器端渲染,使用React服务器端渲染的注意事项有哪些,下面就是实战案例,一起来看一下。
React提供了两个方法 renderToString和 renderToStaticMarkup用来将组件(Virtual DOM)输出成 HTML字符串,这是 React服务器端渲染的基础,它移除了服务器端对于浏览器环境的依赖,所以让服务器端渲染变成了一件有吸引力的事情。
服务器端渲染除了要解决对浏览器环境的依赖,还要解决两个问题:
前后端可以共享代码
前后端路由可以统一处理
React生态提供了很多选择方案,这里我们选用 Redux和 react-router来做说明。
Redux
Redux提供了一套类似 Flux的单向数据流,整个应用只维护一个 Store,以及面向函数式的特性让它对服务器端渲染支持很友好。
2分钟了解 Redux是如何运作的
关于 Store:
整个应用只有一个唯一的 Store
Store对应的状态树(State),由调用一个 reducer函数(root reducer)生成
状态树上的每个字段都可以进一步由不同的 reducer函数生成
Store包含了几个方法比如 dispatch, getState来处理数据流
Store的状态树只能由 dispatch(action)来触发更改
Redux的数据流:
action是一个包含{ type, payload}的对象
reducer函数通过 store.dispatch(action)触发
reducer函数接受(state, action)两个参数,返回一个新的 state
reducer函数判断 action.type然后处理对应的 action.payload数据来更新状态树
所以对于整个应用来说,一个 Store就对应一个 UI快照,服务器端渲染就简化成了在服务器端初始化 Store,将 Store传入应用的根组件,针对根组件调用 renderToString就将整个应用输出成包含了初始化数据的 HTML。
react-router
react-router通过一种声明式的方式匹配不同路由决定在页面上展示不同的组件,并且通过 props将路由信息传递给组件使用,所以只要路由变更,props就会变化,触发组件 re-render。
假设有一个很简单的应用,只有两个页面,一个列表页/list和一个详情页/item/:id,点击列表上的条目进入详情页。
可以这样定义路由,./routes.js
import React from'react';
import{ Route} from'react-router';
import{ List, Item} from'./components';
//无状态(stateless)组件,一个简单的容器,react-router会根据 route
//规则匹配到的组件作为 `props.children`传入
const Container=(props)=>{
return(
<p>{props.children}</p>
);
};
// route规则:
//- `/list`显示 `List`组件
//- `/item/:id`显示 `Item`组件
const routes=(
<Route path="/" component={Container}>
<Route path="list" component={List}/>
<Route path="item/:id" component={Item}/>
</Route>
);
export default routes;从这里开始,我们通过这个非常简单的应用来解释实现服务器端渲染前后端涉及的一些细节问题。
Reducer
Store是由 reducer产生的,所以 reducer实际上反映了 Store的状态树结构
./reducers/index.js
import listReducer from'./list';
import itemReducer from'./item';
export default function rootReducer(state={}, action){
return{
list: listReducer(state.list, action),
item: itemReducer(state.item, action)
};
}rootReducer的 state参数就是整个 Store的状态树,状态树下的每个字段对应也可以有自己的reducer,所以这里引入了 listReducer和 itemReducer,可以看到这两个 reducer的 state参数就只是整个状态树上对应的 list和 item字段。
具体到./reducers/list.js
const initialState= [];
export default function listReducer(state= initialState, action){
switch(action.type){
case'FETCH_LIST_SUCCESS': return [...action.payload];
default: return state;
}
}list就是一个包含 items的简单数组,可能类似这种结构:[{ id: 0, name:'first item'},{id: 1, name:'second item'}],从'FETCH_LIST_SUCCESS'的 action.payload获得。
然后是./reducers/item.js,处理获取到的 item数据
const initialState={};
export default function listReducer(state= initialState, action){
switch(action.type){
case'FETCH_ITEM_SUCCESS': return [...action.payload];
default: return state;
}
}Action
对应的应该要有两个 action来获取 list和 item,触发 reducer更改 Store,这里我们定义 fetchList和 fetchItem两个 action。
./actions/index.js
import fetch from'isomorphic-fetch';
export function fetchList(){
return(dispatch)=>{
return fetch('/api/list')
.then(res=> res.json())
.then(json=> dispatch({ type:'FETCH_LIST_SUCCESS', payload: json}));
}
}
export function fetchItem(id){
return(dispatch)=>{
if(!id) return Promise.resolve();
return fetch(`/api/item/${id}`)
.then(res=> res.json())
.then(json=> dispatch({ type:'FETCH_ITEM_SUCCESS', payload: json}));
}
}isomorphic-fetch是一个前后端通用的 Ajax实现,前后端要共享代码这点很重要。
另外因为涉及到异步请求,这里的 action用到了 thunk,也就是函数,redux通过 thunk-middleware来处理这类 action,把函数当作普通的 action dispatch就好了,比如 dispatch(fetchList())
Store
我们用一个独立的./store.js,配置(比如 Apply Middleware)生成 Store
import{ createStore} from'redux';
import rootReducer from'./reducers';
// Apply middleware here
//...
export default function configureStore(initialState){
const store= createStore(rootReducer, initialState);
return store;
}react-redux
接下来实现<List>,<Item>组件,然后把 redux和 react组件关联起来,具体细节参见 react-redux
./app.js
import React from'react';
import{ render} from'react-dom';
import{ Router} from'react-router';
import createBrowserHistory from'history/lib/createBrowserHistory';
import{ Provider} from'react-redux';
import routes from'./routes';
import configureStore from'./store';
// `INITIAL_STATE`来自服务器端渲染,下一部分细说
const initialState= window.INITIAL_STATE;
const store= configureStore(initialState);
const Root=(props)=>{
return(
<p>
<Provider store={store}>
<Router history={createBrowserHistory()}>
{routes}
</Router>
</Provider>
</p>
);
}
render(<Root/>, document.getElementById('root'));至此,客户端部分结束。
Server Rendering
接下来的服务器端就比较简单了,获取数据可以调用 action,routes在服务器端的处理参考 react-router server rendering,在服务器端用一个 match方法将拿到的 request url匹配到我们之前定义的 routes,解析成和客户端一致的 props对象传递给组件。
./server.js
import express from'express';
import React from'react';
import{ renderToString} from'react-dom/server';
import{ RoutingContext, match} from'react-router';
import{ Provider} from'react-redux';
import routes from'./routes';
import configureStore from'./store';
const app= express();
function renderFullPage(html, initialState){
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<p id="root">
<p>
${html}
</p>
</p>
<script>
window.INITIAL_STATE=${JSON.stringify(initialState)};
</script>
<script src="/static/bundle.js"></script>
</body>
</html>
`;
}
app.use((req, res)=>{
match({ routes, location: req.url},(err, redirectLocation, renderProps)=>{
if(err){
res.status(500).end(`Internal Server Error${err}`);
} else if(redirectLocation){
res.redirect(redirectLocation.pathname+ redirectLocation.search);
} else if(renderProps){
const store= configureStore();
const state= store.getState();
Promise.all([
store.dispatch(fetchList()),
store.dispatch(fetchItem(renderProps.params.id))
])
.then(()=>{
const html= renderToString(
<Provider store={store}>
<RoutingContext{...renderProps}/>
</Provider>
);
res.end(renderFullPage(html, store.getState()));
});
} else{
res.status(404).end('Not found');
}
});
});服务器端渲染部分可以直接通过共用客户端 store.dispatch(action)来统一获取 Store数据。另外注意 renderFullPage生成的页面 HTML在 React组件 mount的部分(<p id="root">),前后端的 HTML结构应该是一致的。然后要把 store的状态树写入一个全局变量(INITIAL_STATE),这样客户端初始化 render的时候能够校验服务器生成的 HTML结构,并且同步到初始化状态,然后整个页面被客户端接管。
最后关于页面内链接跳转如何处理?
react-router提供了一个<Link>组件用来替代<a>标签,它负责管理浏览器 history,从而不是每次点击链接都去请求服务器,然后可以通过绑定 onClick事件来作其他处理。
比如在/list页面,对于每一个 item都会用<Link>绑定一个 route url:/item/:id,并且绑定 onClick去触发 dispatch(fetchItem(id))获取数据,显示详情页内容。
相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!
推荐阅读:
怎样使用JS实现计算圆周率到小数点后100位
怎样使用vue axios给生产与发布环境配置接口地址
如何使用Vue+Nuxt.js实现服务端渲染
这次给大家带来如何使用Vue+Nuxt.js实现服务端渲染,使用Vue+Nuxt.js实现服务端渲染的注意事项有哪些,下面就是实战案例,一起来看一下。
直接使用 Vue构建前端单页面应用,页面源码时只有简单的几行 html,这并不利于网站的 SEO,这时候就需要服务端渲染
2016年 10月 25日,zeit.co背后的团队对外发布了一个 React的服务端渲染应用框架 Next.js
几小时后,一个基于 Vue.js的服务端渲染应用框架应运而生,与 Next.js异曲同工,这就是Nuxt.js
一、快速模板
在已经安装了 vue-cli的前提下,可以快速创建一个 nuxt的项目模板
vue init nuxt-community/starter-template MyProject其中 MyProject是项目文件夹名称,可自定义
通过 npm install(似乎用 yarn install更顺利)安装依赖之后,可以直接 npm run dev在开发环境启动项目
默认启动的地址为 http://localhost:3000/,可以在 package.json中添加以下配置来修改主机端口号
"config":{
"nuxt":{
"host":"0.0.0.0",
"port":"7788"
}
},开发完成后执行 npm run build打包代码,最后 npm start启动服务
二、重要目录
生成的项目目录如下
大部分文件夹名称都是 nuxt默认保留的,不可修改
其中比价比较关键的目录有三个:
1. components组件目录
一般用来存放非页面级别的组件,如 header、footer等公共组件
该目录下的组件具有常规 vue组件的方法和特性,不会被 nuxt.js扩展特性
2. layouts布局目录
可以修改该目录下的 default.vue来修改默认布局
<template>
<p>
<my-header></my-header>
<nuxt/>
<my-footer></my-footer>
</p>
</template>其中<nuxt/>是必需的,页面的主体内容会显示在这里(类似于根节点的<router-view/>)
此外还可以在目录下新增 error.vue作为错误页面,具体的写法可以参考官方文档
3. pages页面目录
用于存放页面级别的组件,nuxt会根据该目录下的页面结构生成路由
比如上图中的页面结构,会生成这样的路由配置:
router:{
routes: [
{
name:'index',
path:'/',
component:'pages/index.vue'
},
{
name:'about',
path:'/about',
component:'pages/about.vue'
},
{
name:'classroom',
path:'/classroom',
component:'pages/classroom.vue',
children: [
{
path:'student',
component:'pages/classroom/student.vue',
name:'student'
},
{//定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue文件或目录
path:':id',
component:'pages/classroom/_id.vue',
name:'classroom-id'
}
]
}
]
}此外,该目录下的 vue组件还具备一些 Nuxt.js提供的特殊功能特性
其中 asyncData方法比较常用,支持异步数据处理
这个方法会在页面组件每次加载之前被调用,然后获取数据并返回给当前组件
asyncData({ params, error}){
return axios.get(`api/posts/${params.id}`)
.then((res)=>{
return{ name: res.data.name}
})
.catch((e)=>{
error({ statusCode: 404, message:'not found'})
})
}asyncData方法的第一个参数为上下文对象 context,具体属性可以查看这里
由于asyncData方法是在组件初始化前被调用的,所以在方法内是没有办法通过 this来引用组件的实例对象
三、使用插件
如果项目中还需要引入其他的第三方插件,可以直接在页面中引入,这样在打包的时候,会将插件打包到页面对应的 js里面
但要是别的页面也引入了同样的插件,就会重复打包。如果没有需要分页打包的需求,这时候可以配置 plugins
以 element-ui为例,在安装了 element-ui之后,在 plugins目录下创建 elementUI.js
然后在根目录的 nuxt.config.js中添加配置项 build.vendor和 plugins
build:{
vendor: ['~/plugins/elementUI.js']
},
plugins: [
{src:'~/plugins/elementUI.js'},
]这里的 plugins属性用来配置 vue.js插件,也就是可以用 Vue.user()方法的插件
默认只需要 src属性,另外还可以配置 ssr: false,让该文件只在客户端被打包引入
如果是像 axios这种第三方(不能 use)插件,只需要在 plugins目录下创建 axios.js
// axios.js
import Vue from'vue'
import axios from'axios'
const service= axios.create({
baseURL:'/api'
})
Vue.prototype.$ajax= axios
export default service然后在 build.vendor中添加配置(不需要配置 plugins)
build:{
vendor: ['~/plugins/axios.js']
}这样在打包的时候,就会把 axios打包到 vendor.js中
四、Vuex状态树
如果在 store目录下创建了 index.js,nuxt.js会根据该目录下的文件创建 Vuex状态树
// store/index.js
import Vue from'vue'
import Vuex from'vuex'
import Axios from'~/plugins/axios.js';
Vue.use(Vuex)
const store=()=> new Vuex.Store({
state:{
author:'WiseWrong',
info:''
},
mutations:{
setInfo(state, val){
state.info= val
}
},
actions:{
loadAboutMeInfo({commit, state}){
return Axios.get(`/about`)
.then(res=>{
console.log('ajax is success')
console.log(res.data.info)
commit('setInfo', res.data.info)
})
.catch(err=>{
console.log('error')
})
}
}
})
export default storeNuxt.js内置引用了 vuex模块,不需要额外安装
上面的代码中,我在 actions中写了一个 loadAboutMeInfo()方法,用来请求/api/about接口
然后在 about.vue页面中调用
// about.vue
<template>
<section class="container">
<p>
<img src="~/assets/about.png" alt="">
</p>
<h1>{{$store.state.info}}</h1>
</section>
</template>
<script>
export default{
fetch({ store}){
return store.dispatch('loadAboutMeInfo')
},
name:'about',
data(){
return{}
}
}
</script>成果演示:
相信看了本文案例你已经掌握了方法,更多精彩请关注Gxl网其它相关文章!
推荐阅读:
如何通过JS判断页面是否有滚动条
怎样使用webpack源码loader机制
vue项目改造SSR(服务端渲染)
缺点:1、SEO问题
2、首屏速度问题
3、消耗性能的问题
优点:
1、更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面
2、首屏渲染速度快
SSR简单来说就是将页面在服务端渲染完成后在客户端直接展示。
index.template.html
server.js
vue项目是通过虚拟 DOM来挂载到html的,所以对spa项目,爬虫才会只看到初始结构。虚拟 DOM,最终要通过一定的方法将其转换为真实 DOM。虚拟 DOM也就是 JS对象,整个服务端的渲染流程就是通过虚拟 DOM的编译成完整的html来完成的。
需要通过Webpack打包生成两份bundle文件:
Client Bundle,给浏览器用。和纯Vue前端项目Bundle类似
Server Bundle,供服务端SSR使用,一个json文件
不管项目先前是什么样子,是否是使用vue-cli生成的。都会有这个构建改造过程。在构建改造这里会用到 vue-server-renderer库,这里要注意的是 vue-server-renderer版本要与Vue版本一样。
打包之后目录结构
vue.config.js
index.template.html
打包成客户端和服务器端
启动node服务
github地址: https://github.com/wang12321/SSR
文章分享结束,vue服务器渲染和vue项目改造SSR(服务端渲染)的答案你都知道了吗?欢迎再次光临本站哦!