闽公网安备 35020302035485号

2./query: 根据卡片的元信息查询图表数据,数据返回之后再进行渲染
// CardComponent.js:
import {connect} from 'react-refetch'
@connect(() => {
return {
metaInfoFetch {
url: '/meta',
andThen = () => ({
query: '/query'
})
}
}
})
class Card {
render() {
const queryResult = this.props.query.value
return <Chart data={queryResult} />
}
}
// 堆代码 duidaima.com
// DashboardComponent.js:
class Dashboard {
return (
<div>
{cards.map(({id, position, size}) =>
<Card id={id} position={position} size={size} />
)}
</div>
)
}
connect能保证 metaInfoFetch在组件加载之初就自动执行,并且执行完毕之后继续执行query查询,并且把结果以属性的形式传递进组件中。但是没想到前端这种「自治」的解决方案却给产品带来了灾难。
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
虽然现在 callee 已经 deprecated 了,setTimeout也可以使用requestAnimationFrame代替。但是它背后的思考方式并没有发生变化。性能优化在日常工作中其实处于很尴尬的位置。例如你花费三天为页面或者 App 开发了一个功能,上线之后大家是有目共睹的。然而如果你花费三天时间告诉大家我进行了一次代码优化,大家会对你的产出有所怀疑。所以在优化之前我们最好确定计划提升的指标以便量化产出。
.Time to Interactive
const promiseDispatcher = new PromiseDispatcher() promiseDispatcher.feed( requestMetaJob, requestDataJob, renderChart )feed 顺序同时也是函数的执行顺序。
promiseDispatcher.feed(
requestMetaJob,
requestDataJob,
renderChart
).then((requestMetaResult, requestDataResult, renderResult) => {
})
我们不支持以上的使用方法并不是因为实现不了,而是从职责上考虑队列不应该承担这样的工作。队列应该只负责分发并且保证成员执行顺序的正确性。如果你还不明白其中的道理,可以参考 dispatcher 角色在 Flux 架构中的功能。return tasks.reduce((prevPromise, currentTask()) => {
return prevPromise.then(chainResults =>
currentTask().then(currentResult =>
[ ...chainResults, currentResult ]
)
);
}, Promise.resolve([]))
然而我们还要兼容同步函数的代码,所以需要对currentTask返回结果是否是Promise类型做判断:// https://stackoverflow.com/questions/27746304/how-do-i-tell-if-an-object-is-a-promise#answer-27746324
function isPromiseObj(obj) {
return obj && obj.then && _.isFunction(obj.then);
}
return tasks.reduce((prevPromise, currentTask()) => {
return prevPromise.then(chainResults => {
let curPromise = currentTask()
curPromise = !isPromiseObj(curPromise) ? Promise.resolve(curPromise) : curPromise
curPromise.then(currentResult => [ ...chainResults, currentResult ])
});
}, Promise.resolve([]))
然而需要考虑更复杂的情况是,有时候仅仅是单个依次执行任务又过于节约了,所以我们要允许多个任务「并发」执行。于是我们决定给允许给 PromiseDispatcher 配置名为 maxParallelExecuteCount 的参数,用于控制最大可并行的执行个数。针对这个需求,代码上也要做一些修改,使用 Promise.all 来处理多个并发的异步操作情况:import _ from 'lodash'
const { maxParallelExecuteCount = 1 } = this.config;
const chunkedTasks = _.chunk(this.tasks, maxParallelExecuteCount);
// 堆代码 duidaima.com
return chunkedTasks.reduce((prevPromise, curChunkedTask) => {
return prevPromise.then(prevResult => {
return Promise.all(
curChunkedTask.map(curTask => {
let curPromise = curTask()
curPromise = !isPromiseObj(curPromise) ? Promise.resolve(curPromise) : curPromise
return curPromise
})
).then(curChunkedResults => [ ...chainResults, curChunkedResults ])
})
}, Promise.resolve([]))
与组件整合// Component App.js:
import { observer, inject } from "mobx-react";
@inject('dashboardStore')
@observer
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
const { reports } = this.props.dashboardStore;
return (
<div>
{reports.map(({ id, data, loading, rendered }) => {
return (
<ChartCard key={id} data={data} loading={loading} rendered={rendered} />
);
})}
</div>
);
}
}
// DashboardStore.js:
export default class DashboardStore {
@observable reports = [...Array(30).keys()].map((item, index) => {
return {
loading: true,
id: index,
data: [],
rendered: false
};
});
constructor() {
autorun(() => {
this.reports.forEach(report => {
const requestMetaJob = () => {
report.loading = true;
return axios.get("/meta");
};
const requestDataJob = () => {
return axios.get("/api").then(() => {
report.loading = false;
report.data = randomData();
});
};
const initializeRendering = () => {
report.rendered = true;
};
promiseDispatcher.feed([requestMetaJob, requestDataJob, initializeRendering]);
});
});
}
}
注意,因为我们无法手动调用组件的 API 触发组件渲染,所以采用标志位rendered被动的触发卡片的渲染。在组件 <ChartCard /> 中只要做简单的判断即可:componentDidUpdate(prevProps) {
if (!prevProps.rendered && this.props.rendered) {
this.renderChart(this.props.data);
}
}
验收
.Load (Red): 1.34s

.Load (Red): 1.59ss
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
const metricName = entry.name;
const time = Math.round(entry.startTime + entry.duration);
console.log(metricName, time);
}
});
observer.observe({ entryTypes: ["paint"] });
打印的结果如下:first-contentful-paint 1023

3.找到之后,再向前追溯到最后一个长任务的执行结束点,那就是我们的要找的 TTI