동형 서버 측 렌더링에 대해 이야기하기 전에는 HTML 구조를 직접 추출할 수 있습니다. 서버 측에서 스타일, 그림 등의 정적 리소스를 도입하는 문제에 대한 해결책을 설명했지만 실제로 관련 작업을 수행하지는 않았습니다. 이 글에서는 스타일을 HTML처럼 바로 보이게 만드는 방법에 대해 설명합니다. 이번 글은 주로 React isomorphism의 스타일 직접 표현을 소개하고 있는데, 편집자는 꽤 좋다고 생각해서 공유하고 참고하겠습니다. 편집자를 따라 살펴보겠습니다. 모두에게 도움이 되기를 바랍니다.
PS: 제가 이해한 바로는 URL을 입력하여 서버에 액세스하기 위한 가져오기 요청을 시작하고 Ajax를 통해 비동기적으로 응답을 얻는 대신 전체 응답을 직접 얻는다는 것입니다.
React 동형화의 핵심 요소
완벽한 구성 요소 속성과 수명 주기, 클라이언트 측 렌더링 타이밍이 React 동형화의 핵심입니다.
DOM 일관성
프런트엔드와 백엔드에서 동일한 구성요소를 렌더링하면 일관된 Dom 구조가 출력됩니다.
다양한 라이프 사이클
서버 측에서는 컴포넌트 라이프 사이클이 컴포넌트WillMount에만 도달하지만 클라이언트 측에서는 완료됩니다.
클라이언트 측 렌더링 타이밍
동형인 경우 서버는 데이터를 결합하여 구성 요소를 완전한 HTML 문자열로 렌더링하고 데이터 상태를 클라이언트에 반환합니다. 직접 사용할 수 있는지 아니면 필요한지 여부를 클라이언트가 결정합니다. 다시 마운트됩니다.
위 내용은 동형/서버사이드 렌더링을 위해 React에서 제공하는 기본 조건입니다. 실제 프로젝트 애플리케이션에서는 다른 코너 문제를 고려해야 합니다. 예를 들어 서버 측에 창 개체가 없고 다른 처리가 수행되어야 합니다. 아래에서는 모바일QQ 홈스쿨 그룹에서 구체적인 실천을 통해 동형 팁과 최적화 결과를 공유하겠습니다
스타일 파일 추가
현재 프로젝트에 스타일 파일이 없어서 먼저 작성해야 합니다. 구성 요소 앱에 대한 스타일 파일을 작성합니다.
종속성 설치
다음 종속성은 나중에 사용됩니다. 먼저 각 종속성의 기능을 자세히 설명합니다.
다음과 같이 코드를 복사하세요.
npm install postcss-loader postcss-import postcss-cssnext postcss-nested postcss-functions css-loader style-loader isomorphic-style-loader --save-dev
Create .pcss file
css 파일의 접미사는 .css이고 less file의 접미사는 .less입니다. 여기서는 PostCSS 플러그인을 사용하여 스타일을 작성하기로 선택했기 때문에 .pcss 접미사를 직접 정의합니다.
// ./src/client/component/app/style.pcss .root { color: red; }
루트 클래스를 설정하고, 스타일은 단순히 색상을 빨간색으로 설정하는 것입니다. 그런 다음 앱 구성요소에서 이를 참조합니다.
// ./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', }), ... ); ...
클라이언트와 서버는 스타일 파일을 처리하기 위해 서로 다른 styleLoader를 사용해야 합니다.
PostCSS 소개
PostCSS는 js를 사용하여 CSS를 변환하는 도구입니다. webpack과 함께 사용되는 로더는 postcss-loader이지만 실제로는 단일 postcss-loader만이 강력한 기능을 달성하기 위해 플러그인과 일치해야 합니다.
1.postcss-import
여기서 이 플러그인을 사용하는 이유는 스타일 파일에서 @importing을 할 때 경로 값을 설정한 후 언제든지 스타일 파일에 추가하기 위해서입니다. 물론 import 'variables.pcss';만 작성하면 됩니다. 해당 파일을 찾을 수 없으면 해당 경로는 무시되고 기본 상대 경로가 검색에 사용됩니다.
2. postcss-cssnext
이 플러그인은 차세대 CSS 구문을 사용할 수 있습니다.
3. postcss-nested
이 플러그인은 쓰기 스타일을 중첩할 수 있습니다.
4. postcss-functions
이 플러그인은 기능을 사용자 정의하고 스타일 파일로 호출할 수 있습니다.
말은 많이 했지만 코드 작성 예를 들어보겠습니다~
클라이언트 디렉터리에 새 스타일 폴더를 추가하여 일부 스타일 재설정, 변수 파일 등을 저장합니다. 그런 다음 두 개의 PCS 파일을 만듭니다.
// ./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
위 내용은 리액트 동형화의 직접적인 스타일에 대한 간략한 논의의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!