前言
創(chuàng)新互聯(lián)建站主要從事網(wǎng)站設計制作、成都網(wǎng)站制作、網(wǎng)頁設計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務。立足成都服務湞江,十余年網(wǎng)站建設經(jīng)驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:18980820575
上文講到通過同構(gòu)服務端渲染,可以直出html結(jié)構(gòu),雖然講解了樣式,圖片等靜態(tài)資源在服務端引入問題的解決方案,但是并沒有實際進行相關操作,這篇文章就講解一下如何讓樣式像html一樣直出。
PS: 直出,我的理解就是輸入url發(fā)起get請求訪問服務端,直接得到完整響應結(jié)果,而不是同過ajax異步去獲取。
React 同構(gòu)的關鍵要素
完善的 Compponent 屬性及生命周期與客戶端的 render 時機是 React 同構(gòu)的關鍵。
DOM 的一致性
在前后端渲染相同的 Compponent,將輸出一致的 Dom 結(jié)構(gòu)。
不同的生命周期
在服務端上 Component 生命周期只會到 componentWillMount,客戶端則是完整的。
客戶端 render 時機
同構(gòu)時,服務端結(jié)合數(shù)據(jù)將 Component 渲染成完整的 HTML 字符串并將數(shù)據(jù)狀態(tài)返回給客戶端,客戶端會判斷是否可以直接使用或需要重新掛載。
以上便是 React 在同構(gòu)/服務端渲染的提供的基礎條件。在實際項目應用中,還需要考慮其他邊角問題,例如服務器端沒有 window 對象,需要做不同處理等。下面將通過在手Q家校群上的具體實踐,分享一些同構(gòu)的 Tips 及優(yōu)化成果
加入樣式文件
目前我們的項目中還不存在任何樣式文件,所以需要先寫一個,就給組件App寫一個樣式文件吧。
安裝依賴
下面這些依賴都是后續(xù)會用到的,先安裝一下,下面會詳細講解每個依賴的作用。
創(chuàng)建.pcss文件
css文件的后綴是.css,less文件的后綴是.less,這里我選擇使用PostCSS配合其插件來寫樣式,所以我就自己定義一個后綴.pcss好了。
// ./src/client/component/app/style.pcss .root { color: red; }
設定一個root類,樣式就是簡單的設置顏色為紅色。然后在App組件里引用它。
// ./src/client/component/app/index.tsx ... import * as styles from './style.pcss'; ... public render() { return ( <div className={styles.root}>hello world</div> ); } ...
這個時候你會發(fā)現(xiàn)編輯器里是這樣的:
出現(xiàn)這個問題是因為ts不知道這種模塊的類型定義,所以我們需要手動加入自定義模塊類型定義。在項目根目錄下新建@types文件夾,在此目錄下建立index.d.ts文件:
// ./@types/index.d.ts declare module '*.pcss' { const content: any; export = content; }
保存之后就不會看到編輯器報錯了,但是terminal里webpack打包會提示出錯,因為我們還沒有加對應的loader。
配置.pcss文件的解析規(guī)則
js都組件化了,css模塊化也是很有必要的,不用再為避免取重復類名而煩惱。我們在base配置里新導出一個方法用以獲取postcss的規(guī)則。
// ./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 文件需要用到三個loader,按處理順序從下往上分別是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', }), ... ); ...
客戶端和服務端處理樣式文件需要使用到不同的styleLoader。
PostCSS簡介
PostCSS是一個使用js來轉(zhuǎn)換css的工具,這個是官方介紹。其配合webpack使用的loader就是postcss-loader,但是只有單個postcss-loader其實沒有什么用,需要配合其插件來實現(xiàn)強大的功能。
1、postcss-import
這個插件我這里使用的原因是為了在樣式文件中@import時避免復雜的路徑編寫,我設定好path值,那么我在其它任何層級下的樣式文件中要引入path對應文件夾里的公共變量樣式文件(假設叫"variables.pcss")時就非常方便,只需要寫import 'variables.pcss';就可以了,當然如果找不到對應的文件,它會忽略path使用默認相對路徑來查找。
2、postcss-cssnext
這個插件可以使用下一代css語法。
3、postcss-nested
這個插件可以嵌套編寫樣式。
4、postcss-functions
這個插件可以自定義函數(shù),并在樣式文件中調(diào)用。
講這么多,寫代碼舉個栗子吧~
我們在client目錄下新增style文件夾,用于存放一些樣式reset,變量文件之類的東西。然后創(chuàng)建兩個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 Modules簡介
簡單來說就是css模塊化,不用再擔心全局類名的問題。我們根據(jù)上述css-loader的options來看:
isomorphic-style-loader
在客戶端,使用style-loader,它會動態(tài)的往dom里插入style元素,而服務端由于缺少客戶端的相關對象及API,所以需要isomorphic-style-loader,目前用到它只是為了避免報錯哈哈,后續(xù)還有大作用,樣式直出全靠它。
打包運行
注意:打包運行之前不要忘了給tsconfig.client.json和tsconfig.server.json引入我們的自定義模塊定義文件index.d.ts,不然webpack編譯就會報找不到pcss這種模塊啦。
// ./src/webpack/tsconfig.client(server).json ... "include": [ ... "../../@types/**/*", ... ] ...
運行結(jié)果如下:
雖然style元素已經(jīng)存在,但是這個是由style-loader生成的,并不是服務端直出的,看page source就知道了。
而且在刷新頁面的時候能很明顯的看到樣式變化閃爍的效果。
直出樣式
我們利用isomorphic-style-loader來實現(xiàn)服務端直出樣式,原理的話根據(jù)官方介紹就是利用了react的context api來實現(xiàn),在服務端渲染的過程中,利用注入的insertCss方法和高階組件(hoc high-order component)來獲取樣式代碼。
安裝依賴
npm install prop-types --save-dev
改寫App組件
根據(jù)其官方介紹,我們在不使用其整合完畢的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組件里的具體內(nèi)容遷移到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方法。根據(jù)其定義,該方法擁有默認值,返回空字符串的函數(shù),即默認沒什么作用,但是可以通過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函數(shù)直接返回renderToString的html字符串,而現(xiàn)在多了一個style,所以我們返回擁有html和style屬性的對象。
疑問四:官方示例css是一個Set類型實例,這里怎么是一個數(shù)組類型實例?
答:Set是es6中新的數(shù)據(jù)結(jié)構(gòu),類似數(shù)組,但可以保證無重復值,只有tsconfig的編譯選項中的target為es6時,且加入es2017的lib時才不會報錯,由于我們的target是es5,所以是數(shù)組,且使用數(shù)組并沒有太大問題。
處理服務端入口文件
由于bundle的render值變更,所以我們也要處理一下。
// ./src/server/index.tsx ... router.get('/*', (ctx: Koa.Context, next) => { // 配置一個簡單的get通配路由 const renderResult = bundle ? bundle.render() : {}; // 獲得渲染出的結(jié)果對象 const { html = '', style = '' } = renderResult; ... ctx.body = ` ... <head> ... ${style ? `<style>${style}</style>` : ''} ... </head> ... `; ... }); ...
直出結(jié)果
樣式直出后的page source:
找回丟失的公共樣式文件
從上面的直出結(jié)果來看,缺少./src/style/index.pcss這個樣式代碼,原因顯而易見,它不屬于任何一個組件,它是公共的,我們在客戶端入口文件里引入了它。對于公共樣式文件,服務端要直出這部分內(nèi)容,可以這么做:
./src/server/bundle.tsx ... import * as commonStyles from '../client/style/index.pcss'; ... const css = [commonStyles._getCss()]; ...
我們利用isomorphic-style-loader提供的api可以得到這部分樣式代碼字符串。這樣就可以得到完整的直出樣式了。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
網(wǎng)頁標題:淺談react同構(gòu)之樣式直出
文章地址:http://redsoil1982.com.cn/article0/gsscoo.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供做網(wǎng)站、自適應網(wǎng)站、移動網(wǎng)站建設、網(wǎng)站排名、微信公眾號、營銷型網(wǎng)站建設
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)