同型サーバーサイドレンダリングについて説明する前に、HTML 構造を直接抽出できます。サーバーサイドにスタイルやピクチャなどの静的リソースを導入する問題の解決策を説明しましたが、実際には関連する操作を実行しませんでした。この記事では、スタイルを HTML のようにまっすぐに表示する方法について説明します。この記事では主に React Isomorphism のスタイル直接表現を紹介します。これが非常に優れていると思いますので、参考にしてください。編集者をフォローして見てみましょう。皆さんのお役に立てれば幸いです。
追記: 率直に言って、私の理解では、URL を入力してサーバーにアクセスするための get リクエストを開始し、ajax を介して非同期に取得するのではなく、完全な応答を直接取得することになります。
React 同型性の重要な要素
Perfect コンポーネントのプロパティとライフサイクル、およびクライアント側のレンダリング タイミングが React 同型性の鍵となります。
DOM の一貫性
フロントエンドとバックエンドで同じコンポーネントをレンダリングすると、一貫した Dom 構造が出力されます。
さまざまなライフサイクル
サーバー側では、コンポーネントのライフサイクルはcomponentWillMountまでに達するだけですが、クライアント側では完了します。
クライアント側のレンダリングのタイミング
同型の場合、サーバーはデータを結合してコンポーネントを完全な HTML 文字列にレンダリングし、データのステータスをクライアントに直接使用できるかどうかを判断します。再マウントされる。
上記は、同型/サーバーサイドレンダリングのために React によって提供される基本的な条件です。実際のプロジェクト アプリケーションでは、サーバー側にウィンドウ オブジェクトがなく、別の処理が必要になるなど、他の重要な問題も考慮する必要があります。以下では、モバイル QQ ホームスクール グループでの特定の実践を通じて、同型のヒントと最適化の結果をいくつか共有します
スタイル ファイルを追加します
現在、プロジェクトにはスタイル ファイルがないため、最初にスタイル ファイルを作成する必要があります。コンポーネント App のスタイル ファイルを作成します。
依存関係のインストール
以下の依存関係は後で使用します。各依存関係の機能については以下で詳しく説明します。
次のようにコードをコピーします:
npm install postcss-loader postcss-import postcss-cssnext postcss-nested postcss-functions css-loader style-loader isomorphic-style-loader --save-dev
Create .pcssファイル
css ファイルのサフィックスは .css で、less ファイルのサフィックスは .less です。ここでは、PostCSS とそのプラグインを使用してスタイルを記述することを選択したため、.pcss のサフィックスを自分で定義します。
// ./src/client/component/app/style.pcss .root { color: red; }
ルートクラスを設定し、単純に色を赤にするスタイルです。次に、それを App コンポーネントで参照します。
// ./src/client/component/app/index.tsx ... import * as styles from './style.pcss'; ... public render() { return ( <p className={styles.root}>hello world</p> ); } ...
この時点で、エディターに次のようなものが表示されます:
この問題は、ts がこのモジュールの型定義を認識していないために発生するため、カスタム モジュールの型定義を手動で追加する必要があります。プロジェクトのルート ディレクトリに新しい @types フォルダーを作成し、このディレクトリにindex.d.ts ファイルを作成します:
// ./@types/index.d.ts declare module '*.pcss' { const content: any; export = content; }
保存後にエディター エラーは表示されませんが、ターミナルの Webpack パッケージ化によりエラーが表示されます。対応するローダーがまだ追加されていないためです。
.pcss ファイルの解析ルールを設定する
JS がコンポーネント化され、CSS のモジュール化も必要になりました。重複したクラス名の回避について心配する必要はなくなりました。 postcss ルールを取得するために、基本構成で新しいメソッドをエクスポートします。
// ./src/webpack/base.ts ... export const getPostCssRule = (styleLoader) => ({ test: /\.pcss$/, use: [ styleLoader, { loader: 'css-loader', options: { camelCase: true, importLoaders: 1, localIdentName: '[path][name]---[local]---[hash:base64:5]', modules: true, }, }, { loader: 'postcss-loader', options: { plugins: () => [ require('postcss-import')({ path: path.join(baseDir, './src/client/style'), }), require('postcss-cssnext'), require('postcss-nested'), require('postcss-functions')({ functions: { x2(v, u) { return v * 2 + (u ? u : 'px'); }, }, }), ], }, }, ], }); ...
上記の方法から、.pcss ファイルを処理するには 3 つのローダーが必要であることがわかります。処理順序は下から postcss-loader、css-loader、変数 styleLoader です。このメソッドがどこで使用されているかを確認できます:
// ./src/webpack/client.ts ... (clientDevConfig.module as webpack.NewModule).rules.push( ... getPostCssRule({ loader: 'style-loader', }), ... ); ...
// ./src/webpack/server.ts ... (clientDevConfig.module as webpack.NewModule).rules.push( ... getPostCssRule({ loader: 'isomorphic-style-loader', }), ... ); ...
クライアントとサーバーは、スタイル ファイルを処理するために異なる styleLoaders を使用する必要があります。
PostCSSの紹介
PostCSSはjsを使ってcssを変換するツールの公式紹介です。 webpack で使用されるローダーは postcss-loader ですが、実際には単一の postcss-loader だけではほとんど役に立ちません。強力な機能を実現するには、そのプラグインと一致させる必要があります。
1. postcss-import
ここでこのプラグインを使用するのは、スタイル ファイルにパス値を設定してから、スタイル ファイルにパス値を設定するときに、複雑なパスの記述を避けるためです。他のレベルでは、パスに対応するフォルダーにパブリック変数スタイル ファイル (「variables.pcss」とします) を導入すると非常に便利です。 もちろん、 import 'variables.pcss'; と記述するだけです。対応するファイルが見つからない場合、そのパスは無視され、デフォルトの相対パスが検索に使用されます。
2. postcss-cssnext
このプラグインは次世代の CSS 構文を使用できます。
3. postcss-nested
このプラグインは、記述スタイルをネストできます。
4. postcss-functions
このプラグインは関数をカスタマイズし、スタイル ファイルで呼び出すことができます。
ここまで述べたので、コードの記述例を示しましょう~
クライアントディレクトリに新しいスタイルフォルダーを追加して、いくつかのスタイルリセットや変数ファイルなどを保存します。次に、2 つの pcss ファイルを作成します:
// ./src/client/style/variables.pcss :root { --fontSizeValue: 16; }
// ./src/client/style/index.pcss @import 'variables.pcss'; body { margin: 0; font-size: x2(var(--fontSizeValue)); }
先ほど書いたindex.pcssの紹介
// ./src/client/index.tsx ... import './style/index.pcss'; ...
CSSモジュールの紹介
つまり、cssのモジュール化であり、グローバルクラス名を気にする必要はありません。上記の CSS ローダー オプションを見てみましょう:
camelCase为true运行使用驼峰写法来写类名
importLoaders的值为N是因为在css-loader之前有N个loader已经处理过文件了,这里的N值是1,因为之前有一个postcss-loader,这个值一定要设置对,否则会影响@import语句,我的这个表述可能不是太正确,详细可参见 Clarify importLoaders documentation? 这个地方详细讲解了,我翻译一下大概意思是,这个属性的值N代表的是对于@import的文件要经过css-loader后面的N个loader的处理,英文不太好,大家可以自行理解。
localIdentName这个就是指生成的类名啦,具体看后续结果截图就一目了然了。
modules为true即启用模块化
isomorphic-style-loader
在客户端,使用style-loader,它会动态的往dom里插入style元素,而服务端由于缺少客户端的相关对象及API,所以需要isomorphic-style-loader,目前用到它只是为了避免报错哈哈,后续还有大作用,样式直出全靠它。
打包运行
注意:打包运行之前不要忘了给tsconfig.client.json和tsconfig.server.json引入我们的自定义模块定义文件index.d.ts,不然webpack编译就会报找不到pcss这种模块啦。
// ./src/webpack/tsconfig.client(server).json ... "include": [ ... "../../@types/**/*", ... ] ...
运行结果如下:
虽然style元素已经存在,但是这个是由style-loader生成的,并不是服务端直出的,看page source就知道了。
而且在刷新页面的时候能很明显的看到样式变化闪烁的效果。
直出样式
我们利用isomorphic-style-loader来实现服务端直出样式,原理的话根据官方介绍就是利用了react的context api来实现,在服务端渲染的过程中,利用注入的insertCss方法和高阶组件(hoc high-order component)来获取样式代码。
安装依赖
npm install prop-types --save-dev
改写App组件
根据其官方介绍,我们在不使用其整合完毕的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 ( <p className={styles.root}>hello world</p> ); } } 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里面(参见其 源码 )。
疑问三:@withStyles是什么语法?
答:这个是装饰器,属于es7。使用该语法,需要配置tsconfig:
// ./tsconfig.json // ./src/webpack/tsconfig.client(server).json { ... "compilerOptions": { ... "experimentalDecorators": true, ... }, ... }
改写服务端bundle文件
由于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,所以是数组,且使用数组并没有太大问题。
处理服务端入口文件
由于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> ... `; ... }); ...
直出结果
样式直出后的page source:
找回丢失的公共样式文件
从上面的直出结果来看,缺少./src/style/index.pcss这个样式代码,原因显而易见,它不属于任何一个组件,它是公共的,我们在客户端入口文件里引入了它。对于公共样式文件,服务端要直出这部分内容,可以这么做:
./src/server/bundle.tsx ... import * as commonStyles from '../client/style/index.pcss'; ... const css = [commonStyles._getCss()]; ...
我们利用isomorphic-style-loader提供的api可以得到这部分样式代码字符串。这样就可以得到完整的直出样式了。
相关推荐:
Node直出理论与实践总结_html/css_WEB-ITnose
以上がReact 同型性の直接スタイルに関する簡単な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。