在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:git-lt/isomorphism-koa2-react-antd开源软件地址:https://github.com/git-lt/isomorphism-koa2-react-antd开源编程语言:JavaScript 85.8%开源软件介绍:Koa2 + React + Redux + antd 同构直出探索由来在出现同构之前,我们使用后端的模板渲染引擎,C#的Razor,java的Velocity, nodejs的ejs,jade等,来渲染页面,输出到浏览器,浏览器异步请求数据,再使用各种渲染引擎来渲染数据至模板, 那么如果有样一个使用场景,加载一个列表数据:
所以服务端渲染是不可或缺的一个环节,如何优化,只要我们前后端使用同一份业务逻辑,共一个技术框架,同一套模板,同一套路由处理逻辑,就能达到我们想要的效果。 ReactJS的生命周期在了解之前,先来重温一下ReactJS的生命周期 ReactJS的生命周期可以分为三个阶段来看:实例化、存在期、销毁期 实例化首次实例化
存在期组件已经存在,状态发生改变时
简单记忆:receiveProps => shouldUpdate => update => render => updated 销毁期componentWillUnmount 生命周期中10个API的作用说明
var React = require("react");
var ReactDOM = require("react-dom");
var NewView = React.createClass({
//1.创建阶段
getDefaultProps:function() {
console.log("getDefaultProps");
return {};
},
//2.实例化阶段
getInitialState:function() {
console.log("getInitialState");
return {
num:1
};
},
//render之前调用,业务逻辑都应该放在这里,如对state的操作等
componentWillMount:function() {
console.log("componentWillMount");
},
//渲染并返回一个虚拟DOM
render:function() {
console.log("render");
return(
<div>
hello <strong> {this.props.name} </strong>
</div>
);
},
//该方法发生在render方法之后。在该方法中,ReactJS会使用render生成返回的虚拟DOM对象来创建真实的DOM结构
componentDidMount:function() {
console.log("componentDidMount");
},
//3.更新阶段
componentWillReceiveProps:function() {
console.log("componentWillReceiveProps");
},
//是否需要更新
shouldComponentUpdate:function() {
console.log("shouldComponentUpdate");
return true;
},
//将要更新 不可以在该方法中更新state和props
componentWillUpdate:function() {
console.log("componentWillUpdate");
},
//更新完毕
componentDidUpdate:function() {
console.log("componentDidUpdate");
},
//4.销毁阶段
componentWillUnmount:function() {
console.log("componentWillUnmount");
},
// 处理点击事件
handleAddNumber:function() {
this.setProps({name:"newName"});
}
});
ReactDOM.render(<NewView name="ReactJS"></NewView>, document.body); ** 因为服务端渲染,不存在挂载组件,所以挂载以后的生命周期将不会在服务端渲染时触发, 所以在做服务端组件状态或数据初始化时,要做特殊处理,后面会讲到 ** Redux的基本概念Redux 提供了一套类似 Flux 的单向数据流,整个应用只维护一个 Store,以及面向函数式的特性让它对服务器端渲染支持很友好。 关于 Store:
Redux 的数据流:
同构的应用场景关键APIReactJS官网提供了两个API用于服务端渲染,使其服务端渲染成为可能:
配合 关键要点
组件共用原理对于整个应用来说,一个 Store 就对应一个 UI 快照,服务器端渲染就简化成了在服务器端初始化 Store,将 Store 传入应用的根组件,针对根组件调用 路由同步场景
共用路由 export default (
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="news" component={News} />
<Route path="about" component={About} />
</Route>
</Router>
) Web页面请求Server端页面请求使用 React-router官网文档有前后端共用路由的相关介绍ServerRendering - React-router export default async (ctx, next) => {
try{
//Server端路由与前端路由共用 **页面路由** ../../../app/routes
const { redirectLocation, renderProps } = await _match({ routes: require('../../../app/routes'), location: ctx.url })
//重定向
if(redirectLocation){
ctx.redirect(redirectLocation.pathname + redirectLocation.search)
}else if(renderProps){
//调用页面渲染控制器,开始服务端渲染
await renderCtrl(ctx, next, renderProps)
}else{
await next()
}
}catch(e){
console.error('Server-Render Error Occurs: %s', e.stack)
await ctx.render('500', {
msg: ctx.app.env === 'development' ? e.message : false
})
}
}
//renderProps:从路由组件中获取的路由与组件的信息
export default async (ctx, next, renderProps) => {
const route = renderProps.routes[renderProps.routes.length - 1]
let prefetchTasks = []
// 遍历路由中注册的组件,创建加载数据请求,至数组中
for (let component of renderProps.components) {
if (component && component.WrappedComponent && component.WrappedComponent.fetch) {
const _tasks = component.WrappedComponent.fetch(store.getState(), store.dispatch)
if (Array.isArray(_tasks)) {
prefetchTasks = prefetchTasks.concat(_tasks)
} else if (_tasks.then) {
prefetchTasks.push(_tasks)
}
}
}
//当所有组件的数据加载完成后,
await Promise.all(prefetchTasks)
// 渲染组件
await ctx.render('index', {
title: config.title,
dev: ctx.app.env === 'development',
//将state输出到页面,用于浏览器端redux初始化state
reduxData: store.getState(),
// render之后生成的HTML字符串在app这个对象中,通过ejs渲染至view中,最后输出
app: renderToString(<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>)
})
} Client端页面请求浏览器端,从服务端注入到全局对象中获取redux需要的应用状态state,初始化state
导入路由共用的配置模块,初始化路由
调用 import React from 'react'
import ReactDOM from 'react-dom'
import routes from '../../app/routes'
import { Provider } from 'react-redux'
// 和服务端共用的redux状态管理
import configureStore from '../../app/store/configureStore'
// 页面加载时,从全局对象中获取服务端注入到页面的State数据
const store = configureStore(window.__REDUX_STATE__)
// 浏览器端使用 ReactDOM.render 初始化页面,首屏渲染
ReactDOM.render(
<Provider store={store}>
{routes}
</Provider>,
document.querySelector('.react-container')
) 数据请求共用Server端api请求Server端由于渲染组件时,不会执行到 渲染部分的代码上面已经展示了,下面来看看给组件添加的静态方法 import { fetchNews } from '../actions/news'
@connect( state => state.news )
class News extends Component{
//这里声明一个数据,为的是可以获取多个接口的数据,接口请求是异步请求,返回之后,render之前调用 Promise.all() 保证所有异步请求完成后,再渲染页面
static fetch (state, dispatch) {
const fetchTasks = []
fetchTasks.push(
dispatch(fetchNews(state))
)
return fetchTasks
}
render(){}
} Client端api请求当页面从服务端返回后,那么浏览器端就接管了页面的控制,比如点击下一页这个功能,数据请求就是 class News extends Component{
static fetch (state, dispatch) {
const fetchTasks = []
fetchTasks.push(
dispatch(fetchNews(state))
)
return fetchTasks
}
getNextPage(){
//调用组件的静态方法异步获取数据
this.constructor.fetch(this.props, this.props.dispatch);
}
render(){
<div style={{ background: '#ECECEC', padding: '30px' }}>
<Button type="primary" loading={!loaded} onClick={this.getNextPage.bind(this)}>
下一页
</Button>
<div>
{ newsList }
</div>
</div>
}
} api请求最终都是调用redux 中的 const fetchStateUrl = __SERVER__
? `http://localhost:${require('../../platforms/common/config').port}/api/news`
: '/api/news'
export function fetchNews(state){
return (dispatch) => {
dispatch(newsRequest())
return fetch(fetchStateUrl)
.then(res => res.json())
.then(data => {
console.log('===>news')
console.log(data)
dispatch(newsSucceed(data))
})
.catch(e => dispatch(newsFailed(e)))
}
} 这里的
应用状态同步状态同步主要使用redux去同步,服务端渲染时,生成一个 state,在返回页面时,将这个state注入页面,浏览器端拿到state,接管页面状态的管理。 服务器端在 ctx.render('index', {
title: config.title,
dev: ctx
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论