compression
页面加载一个很大的瓶颈就是 JS bundle 提及太大,下载它话费了很多时间。
我们应该使用 Gzip 来压缩一下:通过 Node/Express 的服务端很容易实现 Gzip;只需要安装 compression 模块并将它作为一个 Express 中间件使用就可以了。
const express = require('express');
const compression = require('compression');
const app = express();
// 将 compression 作为一个 Express 中间件
app.use(compression());
LocalStorage 缓存
使用 local-storage 可以把所有的状态变更都存储到浏览器上,并在用户数据返回的时候对这个本地状态进行补充。首屏的数据可能是旧的,但是没关系!真实的数据很快就能加载回来,并且这会使得首次加载的体验非常快。
解决方案使用了 Redux,首先需要保证 Redux 状态变化的时候更新 localStorage:
import { LOCAL_STORAGE_REDUX_DATA_KEY } from '../constants';
import { debounce } from '../utils'; // generic debounce util
// 当页面首次加载时,redux actions 会迅速被 dispatch
// 每个节目都要获取它们的剧集,所以最小的 action 数量是 2n (n 是节目的数量)
// 不需要太过于频繁的更新 localStorage,可以做 debounce
// 如果传入 null 则抹去数据,用来在登录登出时消除持久状态
const updateLocalStorage = debounce(
value =>
value !== null
? localStorage.setItem(LOCAL_STORAGE_REDUX_DATA_KEY,value)
: localStorage.removeItem(LOCAL_STORAGE_REDUX_DATA_KEY),2500
);
// store 更新时,将相关部分存储到 localStorage 中
export const handleStoreUpdates = function handleStoreUpdates(store) {
// 忽略 modals 和 flash 消息,他们不需要被存储
const { modals,flash,...relevantState} = store.getState();
updateLocalStorage(JSON.stringify(relevantState));
}
// 在退出登录时用来清除数据的一个函数
export const clearReduxData = () => {
// 立即清除存储在 localStorage 中的数据
window.localStorage.removeItem(LOCAL_STORAGE_REDUX_DATA_KEY);
// 因为删除是同步的,而持久化数据是异步的,因此这里会导致一个微妙的 bug:
// 存储的数据会被删除,但是稍后又会被填充上
// 为了解决这个问题,我们会传入一个 null,来终止当前队列所有的更新
updateLocalStorage(null);
// 我们需要触发异步和同步的操作。
// 同步操作保证数据可以立刻被删除,所以如果用户点击退出后立刻关闭页面,数据也能被删除
};
下一步,需要让 Redux store 订阅这个函数,并用前一次会话的数据对它进行初始化。
const localState = JSON.parse(
localStorage.getItem(LOCAL_STORAGE_REDUX_DATA_KEY) || '{}'
);
const store = configureStore(history,localState);
store.subscribe(() => { handleStoreUpdates(store); });
注,subscribe 方法的一点讲解:
使用方法:store.subscribe(listener)
作用是添加一个变化监听器。
每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。
你可以在回调函数里调用 getState() 来拿到当前 state。
在内容已经出现的情况下,页面仍在继续变化,这是一种非常流行的技术,可以让页面更快地展现,并且当新的内容可用时,页面发生更新。
懒加载
在大型桌面窗口上,每屏也许只能看到几张图片,但是在页面加载的时候却会一次性获取全部。
值得庆幸的是,有一个很棒的库 react-lazyload 提供了非常便利的懒加载功能。代码示例如下:
import LazyLoad from 'react-lazyload';
// In some render method somewhere:
<LazyLoad once height={UNITS_IN_PX[6]} offset={50}>
<img srcSet={`...omitted`} sizes={`...omitted`} />
</LazyLoad>
服务端渲染
可以考虑的一个途径是,在服务端渲染一个 “shell” —— 一个有正确布局的占位图,只是没有数据。
但是会一个问题,因为客户端已经通过 localStorage 获取前一次会话的数据了,并且它使用这个数据进行了初始化。但是此时服务端是不知情的,所以我需要处理客户端与服务器之间的标记不匹配。
虽然我可以通过 SSR 将我的首次有效渲染时间减少半秒,但是在那时整个网站都是不能交互的;当一个网站看起来已经准备好了但其实不是的时候,让人觉得非常奇怪。 另外,SSR 也会增加复杂性,并且降低开发速度。性能很重要,但是足够好就够了。