Skip to content

1.初始化项目 ​

本节目标:理解“1.初始化项目”的核心思路,并能把它用于实际开发或面试表达。 1.初始化项目 yarn init -yyarn add react react-dom lodash bootstrap is-array reselect redux react-tiny-virtual-list yarn add webpack webpack-cli webpack-dev-server html-webpack-plugin optimize-css-assets-webpack-plugin babel-loader @babel/core @babel/preset-env @babel/preset-react style-loader css-loader postcss-loader html-webpack-externals-plugin @babel/plugin-syntax-class-propertiesmini-css-extract-plugin--dev 2.编译阶段的优化

  • 开发环境时重复构建更快
    • include
    • resolve
    • alias
    • external
    • 编译缓存
    • 开启多进程
  • 生产环境时文件更小,加载更快
    • 开启tree-sharking
    • scope-hosting
    • splitChunks
    • 提供node的空mocks
    • 持久化缓存

2.1 webpack.config.js constpath = require('path');constwebpack = require('webpack');constHtmlWebpackPlugin = require('html-webpack-plugin');constTerserPlugin = require('terser-webpack-plugin');constOptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');constbootstrap = path.resolve('node_modules/bootstrap/dist/css/bootstrap.css');constHtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');constMiniCssExtractPlugin = require('mini-css-extract-plugin');constshouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';module.exports = ({ development, production }) =>{ constisEnvDevelopment = development === 'development'; constisEnvProduction = production === 'production'; constgetStyleLoaders = (cssOptions) =>{ constloaders = [ isEnvDevelopment && require.resolve('style-loader'), isEnvProduction && MiniCssExtractPlugin.loader, { loader: require.resolve('css-loader'), options: cssOptions, }, 'postcss-loader', ].filter(Boolean); returnloaders; }; return{ mode: isEnvProduction ? 'production': isEnvDevelopment ? 'development': 'development', devtool: isEnvProduction ? shouldUseSourceMap ? 'source-map': false: isEnvDevelopment && 'cheap-module-source-map', cache: { type: 'filesystem'}, entry: { main: './src/index.js'}, optimization: { minimize: isEnvProduction, minimizer: [ newTerserPlugin({ parallel: true}), newOptimizeCSSAssetsPlugin() ], splitChunks: { chunks: 'all', minSize: 0, minRemainingSize: 0, maxSize: 0, minChunks: 1, maxAsyncRequests: 30, maxInitialRequests: 30, enforceSizeThreshold: 50000, cacheGroups: { defaultVendors: { test: /[\/]node_modules[\/]/, priority: -10, reuseExistingChunk: true}, default: { minChunks: 2, priority: -20, reuseExistingChunk: true} } }, runtimeChunk: { name: entrypoint=>runtime-${entrypoint.name}, }, moduleIds: isEnvProduction ? 'deterministic': 'named', chunkIds: isEnvProduction ? 'deterministic': 'named'}, resolve: { modules: [path.resolve('node_modules')], extensions: ['.js'], alias: { bootstrap }, fallback: { crypto: false, buffer: false, stream: false} }, module: { rules: [ { test: /.js$/, use: [ { loader: 'babel-loader', options: { cacheDirectory: true, presets: [ "@babel/preset-react"], plugins:[ "@babel/plugin-proposal-class-properties"] } } ], include: path.resolve('src'), exclude: /node_modules/}, { test: /.css$/, use: getStyleLoaders({ importLoaders: 1}) } ] }, devServer: {}, plugins: [ newHtmlWebpackPlugin( Object.assign( {}, { inject: true, template: './public/index.html'}, isEnvProduction ? { minify: { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, useShortDoctype: true, removeEmptyAttributes: true, removeStyleLinkTypeAttributes: true, keepClosingSlash: true, minifyJS: true, minifyCSS: true, minifyURLs: true, }, } : undefined) ), newHtmlWebpackExternalsPlugin({ externals: [ { module: 'lodash', entry: "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.js", global: '_', }, ], }), ] }} 2.2 src\index.js src\index.js importReact from'react';importReactDOM from'react-dom';ReactDOM.render( <h1>hello</h1>,document.getElementById('root')); 2.3 public\index.html public\index.html <!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>react</title></head><body><divid="root"></div></body></html> 2.4 package.json package.json { "scripts": { "build": "webpack --env=production", "start": "webpack serve --env=development"},} 2.5 访问 curl http://localhost:8080/page1.htmlcurl http://localhost:8080/page2.html3.路由切换优化3.1 src\index.js importReact from'react';importReactDOM from'react-dom';import{HashRouter asRouter,Route,Link} from'react-router-dom';import{dynamic} from'./utils';constLoadingHome = dynamic(()=>import('./components/Home'));constLoadingUser = dynamic(()=>import('./components/User'));ReactDOM.render( <Router><ul><li><Linkto="/">Home</Link></li><li><Linkto="/user">User</Link></li></ul><Routepath="/"exact={true}component={LoadingHome}/><Routepath="/user"component={LoadingUser}/></Router>,document.getElementById('root')); 3.2 src\utils.js src\utils.js constLoading = ()=><div>Loading</div>;exportfunctiondynamic(loadComponent) { constLazyComponent = lazy(loadComponent) return()=>( <React.Suspensefallback={<Loading/>}> <LazyComponent/></React.Suspense>)}functionlazy(load) { returnclassextendsReact.Component{ state = { Component: null} componentDidMount() { load().then(result=>{ this.setState({ Component: result.default}); }); } render() { let{ Component } = this.state; returnComponent && <Component/>; } }} 4.更新阶段优化4.1 PureComponent4.1.1 src\index.js src\index.js importReact from'react';importReactDOM from'react-dom';importApp from'./App';ReactDOM.render(<App/>,document.getElementById('root')); 4.1.2 src\App.js src\App.js importReact from'react';import{PureComponent,memo} from'./utils';exportdefaultclassAppextendsReact.Component{ constructor(props){ super(props); this.state = {title:'计数器',number:0} } add = (amount)=>{ this.setState({number:this.state.number+amount}); } render(){ console.log('App render'); return( <div><Counternumber={this.state.number}/><buttononClick={()=>this.add(1)}>+1</button><buttononClick={()=>this.add(0)}>+0</button><ClassTitletitle={this.state.title}/><FunctionTitletitle={this.state.title}/></div>) }}classCounterextendsPureComponent{ render(){ console.log('Counter render'); return( <p>{this.props.number}</p>) }}classClassTitleextendsPureComponent{ render(){ console.log('ClassTitle render'); return( <p>{this.props.title}</p>) }}constFunctionTitle = memo(props=>{ console.log('FunctionTitle render'); return<p>{props.title}</p>;}); 4.1.3 src\utils.js src\utils.js importReact from'react';exportclassPureComponentextendsReact.Component{ shouldComponentUpdate(nextProps,nextState){ return!shallowEqual(this.props,nextProps)||!shallowEqual(this.state,nextState) }}exportfunctionmemo(OldComponent){ returnclassextendsPureComponent{ render(){ return<OldComponent{...this.props}/>}}}exportfunctionshallowEqual(obj1,obj2){ if(obj1 === obj2) returntrue; if(typeofobj1 !== 'object'|| obj1 ===null|| typeofobj2 !== 'object'|| obj2 ===null){ returnfalse; } letkeys1 = Object.keys(obj1); letkeys2 = Object.keys(obj2); if(keys1.length !== keys2.length){ returnfalse; } for(letkey ofkeys1){ if(!obj2.hasOwnProperty(key) || obj1[key]!== obj2[key]){ returnfalse; } } returntrue;} 4.2 immutable4.2.1 src\App.js src\App.js import React from 'react';import {PureComponent} from './utils';+import { Map } from "immutable";export default class App extends React.Component{ constructor(props){ super(props);+ this.state = {count:Map({ number: 0 })}} add = (amount)=>{+ let count = this.state.count.set('number',this.state.count.get('number') + amount);+ this.setState({count});} render(){ console.log('App render'); return ( <div> <Counter number={this.state.count.get('number')}/> <button onClick={()=>this.add(1)}>+1</button> <button onClick={()=>this.add(0)}>+0</button> </div> ) }}class Counter extends PureComponent{ render(){ console.log('Counter render'); return ( <p>{this.props.number}</p> ) }} 4.2.2 src\utils.js src\utils.js import React from 'react';+import { Map,is } from "immutable";export class PureComponent extends React.Component{ shouldComponentUpdate(nextProps,nextState){ return !shallowEqual(this.props,nextProps)||!shallowEqual(this.state,nextState) }}export function memo(OldComponent){ return class extends PureComponent{ render(){ return <OldComponent {...this.props}/> }}}export function shallowEqual(obj1,obj2){ if(obj1 === obj2)return true; if(typeof obj1 !== 'object' || obj1 ===null || typeof obj2 !== 'object' || obj2 ===null){return false; } let keys1 = Object.keys(obj1); let keys2 = Object.keys(obj2); if(keys1.length !== keys2.length){ return false; } for(let key of keys1){+ if (!obj2.hasOwnProperty(key) || !is(obj1[key],obj2[key])) {return false; } } return true;} 4.3 reselect import{createStore} from'redux';import{ createSelector } from'reselect';letinitialState = { count:{number:0}, todos:[{text:'没完成的事',completed:false},{text:'完成的事',completed:true}], filter:true};constreducer = (state=initialState,action)=>{ switch(action.type){ case'ADD': return{...state,count:{number:state.count.number+1}}; default: returnstate; }}letstore = createStore(reducer);exportconsttodosSelector = (state) =>state.todos;exportconstfilterSelector = (state) =>state.filter;exportconstvisibleTodosSelector = createSelector( [todosSelector,filterSelector], (todos,filter)=>{ console.log('计算visibleTodos'); returntodos.filter(item=>item.completed == filter); });constrender = ()=>{ letstate = store.getState(); console.log(state); conststate1 = visibleTodosSelector(state); console.log(state1);}store.subscribe(render);render();store.dispatch({type:'ADD'}); 5.大数据渲染5.1 时间分片5.1.1 优化前 src\Home.js importReact from'react';exportdefaultclassHomeextendsReact.Component{ state={ list: [] } handleClick=()=>{ letstarTime = newDate().getTime(); this.setState({ list: newArray(30000).fill(0) },()=>{ constend = newDate().getTime() console.log( (end - starTime ) / 1000+ '秒') }) } render(){ return( <ul><buttononClick={this.handleClick}>点击</button>{ this.state.list.map((item,index)=>( <likey={index}>{ index}</li>)) } </ul>) }} 5.1.2 优化后 src\Home.js import React from 'react';export default class Home extends React.Component{ state={ list: [] }+ handleClick=()=>{+ this.timeSlice(550);+ }+ timeSlice = (times)=>{+ //requestIdleCallback+ requestAnimationFrame(()=>{+ let minus = times>=100?100:times;+ times-=minus;+ this.setState({+ list:[...this.state.list,...new Array(minus).fill(0)]+ },()=>{+ if(times>0){+ this.timeSlice(times);+ } + });+ });+ }render(){ return ( <ul> <button onClick={ this.handleClick }>点击</button> { this.state.list.map((item,index)=>( <li key={index} >{index+1}</li> )) } </ul> ) }} 5.2 虚拟列表5.2.1 src\index.js importReact from'react';import{ render } from'react-dom';//import VirtualList from 'react-tiny-virtual-list';importVirtualList from'./components/VirtualList';constdata = newArray(30).fill(0); render( <VirtualListwidth='50%'height={500}itemCount={data.length}itemSize={50}renderItem={(data)=>{ let { index, item, style } = data; console.log(data); return ( <divkey={index}style={{...style, backgroundColor:index% 2=== 0? 'green' :'orange' }}>{index+1} </div>) } } />, document.getElementById('root')); 5.2.2 VirtualList.js src\components\VirtualList.js importReact from'react';exportdefaultclassIndexextendsReact.Component{ scrollBox = React.createRef() state = {start: 0} handleScroll = ()=>{ const{ itemSize } = this.props; const{ scrollTop } = this.scrollBox.current; conststart = Math.floor(scrollTop / itemSize); this.setState({start}) } render() { const{ height, width, itemCount, itemSize, renderItem } = this.props; const{ start } = this.state; letend = start + Math.floor(height/itemSize)+1; end = end>itemCount?itemCount:end; constvisibleList = newArray(end - start).fill(0).map((item,index)=>({index:start+index})); conststyle = {position:'absolute',top:0,left:0,width:'100%', height: itemSize}; return( <divstyle={{overflow:'auto',willChange:'transform', height,width}} ref={this.scrollBox}onScroll={this.handleScroll}><divstyle={{position:'absolute',width:'100%',height:${itemCount* itemSize}px}}>{ visibleList.map(({index}) => renderItem({ index, style:{...style,top:itemSize*index} })) } </div></div>) }} 6.React 性能分析器

  • React 16.5 增加了对新的开发者工具 DevTools 性能分析插件的支持
  • 此插件使用 React 实验性的 Profiler API 来收集有关每个组件渲染的用时信息,以便识别 React 应用程序中的性能瓶颈
  • react-devtools 将为支持新的 Profiler API 的应用显示Profiler 选项卡
  • 浏览 commits(Browsing commits)
  • 过滤 commits(Filtering commits)
  • 火焰图表(Flame chart)
  • 排序图表(Ranked chart)

7.其它性能优化

本站总访问 — 次