源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

浅谈react 同构之样式直出

  • 时间:2022-07-08 09:01 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:浅谈react 同构之样式直出
[b]前言[/b] 上文讲到通过同构服务端渲染,可以直出html结构,虽然讲解了样式,图片等静态资源在服务端引入问题的解决方案,但是并没有实际进行相关操作,这篇文章就讲解一下如何让样式像html一样直出。 PS: 直出,我的理解就是输入url发起get请求访问服务端,直接得到完整响应结果,而不是同过ajax异步去获取。 [b]React 同构的关键要素[/b] 完善的 Compponent 属性及生命周期与客户端的 render 时机是 React 同构的关键。 [b]DOM 的一致性[/b] 在前后端渲染相同的 Compponent,将输出一致的 Dom 结构。 [b]不同的生命周期[/b] [b]在服务端上 Component 生命周期只会到 componentWillMount,客户端则是完整的。 [/b] [b]客户端 render 时机[/b] 同构时,服务端结合数据将 Component 渲染成完整的 HTML 字符串并将数据状态返回给客户端,客户端会判断是否可以直接使用或需要重新挂载。 以上便是 React 在同构/服务端渲染的提供的基础条件。在实际项目应用中,还需要考虑其他边角问题,例如服务器端没有 window 对象,需要做不同处理等。下面将通过在手Q家校群上的具体实践,分享一些同构的 Tips 及优化成果 [b]加入样式文件[/b] 目前我们的项目中还不存在任何样式文件,所以需要先写一个,就给组件App写一个样式文件吧。 [b]安装依赖[/b] 下面这些依赖都是后续会用到的,先安装一下,下面会详细讲解每个依赖的作用。
[url=https://github.com/webpack-contrib/css-loader/issues/228]Clarify importLoaders documentation?[/url] 这个地方详细讲解了,我翻译一下大概意思是,这个属性的值N代表的是对于@import的文件要经过css-loader后面的N个loader的处理,英文不太好,大家可以自行理解。 [*]localIdentName这个就是指生成的类名啦,具体看后续结果截图就一目了然了。[/*] [*]modules为true即启用模块化[/*] [/list] [b]isomorphic-style-loader[/b] 在客户端,使用style-loader,它会动态的往dom里插入style元素,而服务端由于缺少客户端的相关对象及API,所以需要isomorphic-style-loader,目前用到它只是为了避免报错哈哈,后续还有大作用,样式直出全靠它。 [b]打包运行[/b] 注意:打包运行之前不要忘了给tsconfig.client.json和tsconfig.server.json引入我们的自定义模块定义文件index.d.ts,不然webpack编译就会报找不到pcss这种模块啦。
// ./src/webpack/tsconfig.client(server).json
...
"include": [
  ...
  "../../@types/**/*",
  ...
]
...
运行结果如下: [img]http://files.jb51.net/file_images/article/201711/201711070849025.png[/img] 虽然style元素已经存在,但是这个是由style-loader生成的,并不是服务端直出的,看page source就知道了。 [img]http://files.jb51.net/file_images/article/201711/201711070849026.png[/img] 而且在刷新页面的时候能很明显的看到样式变化闪烁的效果。 [b]直出样式[/b] 我们利用isomorphic-style-loader来实现服务端直出样式,原理的话根据官方介绍就是利用了react的context api来实现,在服务端渲染的过程中,利用注入的insertCss方法和高阶组件(hoc high-order component)来获取样式代码。 [b]安装依赖[/b]
npm install prop-types --save-dev
[b]改写App组件[/b] 根据其官方介绍,我们在不使用其整合完毕的isomorphic router的情况下,需要写一个Provider给App组件:
// ./src/client/component/app/provider.tsx

import * as React from 'react';

import * as PropTypes from 'prop-types';

class AppProvider extends React.PureComponent<any, any> {
 public static propTypes = {
  context: PropTypes.object,
 };

 public static defaultProps = {
  context: {
   insertCss: () => '',
  },
 };

 public static childContextTypes = {
  insertCss: PropTypes.func.isRequired,
 };

 public getChildContext() {
  return this.props.context;
 }

 public render() {
  return this.props.children || null;
 }
}

export default AppProvider;

将原App组件里的具体内容迁移到AppContent组件里去:
// ./src/client/component/app/content.tsx

import * as React from 'react';

import * as styles from './style.pcss';

/* tslint:disable-next-line no-submodule-imports */
import withStyles from 'isomorphic-style-loader/lib/withStyles';

@withStyles(styles)
class AppContent extends React.PureComponent {
 public render() {
  return (
   <div className={styles.root}>hello world</div>
  );
 }
}

export default AppContent;

新的App组件:
// ./src/client/component/app/index.tsx

import * as React from 'react';

import AppProvider from './provider';

import AppContent from './content';

class App extends React.PureComponent {
 public render() {
  return (
   <AppProvider>
    <AppContent />
   </AppProvider>
  );
 }
}

export default App;

疑问一:AppProvider组件是做什么的? 答:Provider的意思是 供应者,提供者 。顾名思义,AppProvider为其后代组件提供了一些东西,这个东西就是context,它有一个insertCss方法。根据其定义,该方法拥有默认值,返回空字符串的函数,即默认没什么作用,但是可以通过props传入context来达到自定义的目的。通过设定childContextTypes和getChildContext,该组件后代凡是设定了contextTypes的组件都会拥有this.context对象,而这个对象正是getChildContext的返回值。 疑问二:AppContent为何要独立出去? 答:接上一疑问,AppProvider组件render其子组件,而要使得context这个api生效,其子组件必须是定义了contextTypes的,但是我们并没有看见AppContent有这个定义,这个是因为这个定义在高阶组件withStyles里面(参见其 [url=https://github.com/kriasoft/isomorphic-style-loader/blob/master/src/withStyles.js#L14-L16]源码[/url] )。 疑问三:@withStyles是什么语法? 答:这个是装饰器,属于es7。使用该语法,需要配置tsconfig:
// ./tsconfig.json
// ./src/webpack/tsconfig.client(server).json

{
 ...
 "compilerOptions": {
  ...
  "experimentalDecorators": true,
  ...
 },
 ...
}

[b]改写服务端bundle文件[/b] 由于App组件的改写,服务端不能再复用该组件,但是AppProvider和AppContent目前还是可以复用的。
// ./src/server/bundle.tsx

import * as React from 'react';

/* tslint:disable-next-line no-submodule-imports */
import { renderToString } from 'react-dom/server';

import AppProvider from '../client/component/app/provider';

import AppContent from '../client/component/app/content';

export default {
 render() {
  const css = [];
  const context = { insertCss: (...styles) => styles.forEach((s) => css.push(s._getCss())) };
  const html = renderToString(
   <AppProvider context={context}>
    <AppContent />
   </AppProvider>,
  );
  const style = css.join('');
  return {
   html,
   style,
  };
 },
};

这里我们传入了自定义的context对象,通过css这个变量来存储style信息。我们原先render函数直接返回renderToString的html字符串,而现在多了一个style,所以我们返回拥有html和style属性的对象。 疑问四:官方示例css是一个Set类型实例,这里怎么是一个数组类型实例? 答:Set是es6中新的数据结构,类似数组,但可以保证无重复值,只有tsconfig的编译选项中的target为es6时,且加入es2017的lib时才不会报错,由于我们的target是es5,所以是数组,且使用数组并没有太大问题。 [b]处理服务端入口文件[/b] 由于bundle的render值变更,所以我们也要处理一下。
// ./src/server/index.tsx

...
router.get('/*', (ctx: Koa.Context, next) => { // 配置一个简单的get通配路由
 const renderResult = bundle ? bundle.render() : {}; // 获得渲染出的结果对象
 const { html = '', style = '' } = renderResult;
 ...
 ctx.body = `
  ...
  <head>
   ...
   ${style ? `<style>${style}</style>` : ''}
   ...
  </head>
  ...
 `;
 ...
});
...

[b]直出结果[/b] 样式直出后的page source: [img]http://files.jb51.net/file_images/article/201711/201711070849037.png[/img] 找回丢失的公共样式文件 从上面的直出结果来看,缺少./src/style/index.pcss这个样式代码,原因显而易见,它不属于任何一个组件,它是公共的,我们在客户端入口文件里引入了它。对于公共样式文件,服务端要直出这部分内容,可以这么做:
./src/server/bundle.tsx

...
import * as commonStyles from '../client/style/index.pcss';
...
const css = [commonStyles._getCss()];
...

我们利用isomorphic-style-loader提供的api可以得到这部分样式代码字符串。这样就可以得到完整的直出样式了。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程素材网。
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部