react 16 ssr的重构踩坑

react

ssr

  1. 服务端不能识别前端的window。特别是首屏渲染的数据需要用到window对象(比如href += location.search);
  2. 服务端不能加载图片,css文件。

require.extensions['.less'] = function () {

return null;

};

global.__DEV__ = !bytedEnv.isProd();

global.__SERVER__ = true; // 代码服务端环境

global.window = {

location: {

search: ''

}

};

//做了个中间件事实修改window的属性。

module.exports = () => async (ctx, next) => {

global.window = {

location: {

search: ctx.search,

host: ctx.host,

protocol: ctx.protocol

}

};

await next();

};

客户端需要全局变量来表示是否是客户端环境来选择加载某些库。

  1. 首屏从localstorage取数据,并不能服务端渲染

    isSsr ? <div className="channel-bar">: <ChannelBar />

这里用了hydrate而不是render渲染react组件,导致首刷的dom被尽可能的复用。

导致的问题是ChannelBar的最外层dom被复用。如果服务端渲染为空div占位的话,上面的class channel-bar就会丢失。

所以首屏渲染最好还是要保证首刷客户端和服务端一样才行。

这种问题官方推荐的做法是在react生命周期里渲染首刷不同的部分。不过这种做法必然会导致组件rerender。

解决方案:ChannelBar的useEffect里去localstorage拿数据,这样保证首刷一致。

react 16 hooks问题

function OnePxBorder({ color }) {

const style = {

backgroundColor: color,

height: '1px'

};

return <div className="one-px" style={style} />;

}

对于这样一个组件,const style语句等语句(定义函数等)会在组件rerender的时候重新执行。这样会导致定义在组件内的函数多次定义。

解决方案:无关state的函数定义在react组件外。

function Channel() {

// Declare a new state variable, which we'll call "count"

const [count, setCount] = useState(0);

useEffect(() => {

setCount(1);

});

return (

<div>

<p>You clicked {count} times</p>

<button onClick={() => setCount(count + 1)}>

Click me

</button>

</div>

);

}

useEffect会在每次render的时候重新执行,以上代码,count再点也是1;

useEffect第二个参数传空数组可保证函数只执行一遍。

 useEffect(() => {

setCount(1);

}, []);

这样实现初始化为1的效果。

function Channel() {

const [count, setCount] = useState(0);

useEffect(() => {

console.log('bind')

const timer = setInterval(() => {

setCount(count + 1);

}, 1000);

return () => {

console.log('re bind')

clearInterval(timer);

};

});

return (

<div>

<p>You clicked {count} times</p>

<button onClick={() => setCount(count + 1)}>

Click me

</button>

</div>

);

}

如前,可以看到setInterval被触发,被解绑,被触发,再解绑。

如果加上useEffect(() => {}, []);

会导致count 永远是1。

原因是useEffect如果不重新执行,拿不到最新的state。拿到的state总是最早的state也就是0。

暂时没有好的方法。现在写为useEffect(() => {}, [count]);

如果遇到只能初始化一次的,比如某些插件。可以用事件系统每次拿最新的函数去执行:

import { EventEmitter } from 'events';

const event = new EventEmitter();

new xxx({ // 此插件只运行一次。回调总执行新的doSomething

onFinish: () => {

event.emit('some_event');

}

});

function Components({ doSomething }) {

useEffect(() => {

event.on('some_event', doSomething);

return () => {

event.removeListener('some_event', doSomething);

};

});

}

render函数被重复执行

有这样一个组件,loading是个state,每次setloadding会导致list重新执行。

listEl需要数据处理,渲染。每次setloadding,就会处理一遍数据。

{ listEl }

{

loading && <div>'loading.....'</div>

}

处理方案:useMemo Hooks

useMemo能让我们保存一段dom。

const createMemo = function() {

return useMemo(() => {

// handle data

}, [data.length])

}

这样使得data.length变了之后useMemo hooks里的代码才会重新执行。

useMemo的第二个参数是用Object.is判断的,不能直接写个Object,react不会帮你deep assert。

react 16

如官网所说:react16需要set和map环境。而import的包不会被babel编译,所以需要手动引入map/set

import 'core-js/es6/map';

import 'core-js/es6/set';

import React from 'react';

import ReactDOM from 'react-dom';

ReactDOM.render(

<h1>Hello, world!</h1>,

document.getElementById('root')

);

React 16 depends on the collection types Map and Set. If you support older browsers and devices which may not yet provide these natively (e.g. IE < 11) or which have non-compliant implementations (e.g. IE 11), consider including a global polyfill in your bundled application, such as core-js or babel-polyfill.

服务端http请求

服务端请求不带header。需要把header带上。

首次请求set到客户端的cookie(用户标识),在服务端首刷请求的时候并不能取得。需要手动修改设置cookie。

 if (!reqestHeader.cookie) {

reqestHeader.cookie = `uid=${ctx.u_id}; utm_source=${utmSource}`;

}

css module问题

css全局作用域。不想用插件。

靠规范解决。

编译

gulu要求目录文件是app,需要编译要按gulu目录来。

prod环境找问题比较麻烦,对gulu这种框架,需要看他源码来找,没得文档。

以上是 react 16 ssr的重构踩坑 的全部内容, 来源链接: utcz.com/z/381631.html

回到顶部