写个webplugin 记录了webpack
在一次compilation
所用的时间
const chalk = require('chalk') /* console 颜色 */
var slog = require('single-line-log'); /* 单行打印 console */
class ConsolePlugin {
constructor(options){
this.options = options
}
apply(compiler){
/**
* Monitor file change 记录当前改动文件
*/
compiler.hooks.watchRun.tap('ConsolePlugin', (watching) => {
const changeFiles = watching.watchFileSystem.watcher.mtimes
for(let file in changeFiles){
console.log(chalk.green('当前改动文件:'+ file))
}
})
/**
* before a new compilation is created. 开始 compilation 编译 。
*/
compiler.hooks.compile.tap('ConsolePlugin',()=>{
this.beginCompile()
})
/**
* Executed when the compilation has completed. 一次 compilation 完成。
*/
compiler.hooks.done.tap('ConsolePlugin',()=>{
this.timer && clearInterval( this.timer )
const endTime = new Date().getTime()
const time = (endTime - this.starTime) / 1000
console.log( chalk.yellow(' 编译完成') )
console.log( chalk.yellow('编译用时:' + time + '秒' ) )
})
}
beginCompile(){
const lineSlog = slog.stdout
let text = '开始编译:'
/* 记录开始时间 */
this.starTime = new Date().getTime()
this.timer = setInterval(()=>{
text += '█'
lineSlog( chalk.green(text))
},50)
}
}
webpack 优化项
- include 或 exclude 限制 loader 范围。
{
test: /\.jsx?$/,
exclude: /node_modules/,
include: path.resolve(__dirname, '../src'),
use:['happypack/loader?id=babel']
// loader: 'babel-loader'
}
2. happypack多进程编译 和 缓存babel编译过的文件
// 加 pluglin
new HappyPack({
id:'babel',
loaders:['babel-loader?cacheDirectory=true']
})
3. tree Shaking 删除冗余代码
4. 按需加载,按需引入。
.babelrc
增加对 antd
样式按需引入
["import", {
"libraryName":
"antd",
"libraryDirectory": "es",
"style": true
}]
二 路由懒加载,路由监听器
传统路由
<Switch>
<Route path={'/index'} component={Index} ></Route>
<Route path={'/list'} component={List} ></Route>
<Route path={'/detail'} component={ Detail } ></Route>
<Redirect from='/*' to='/index' />
</Switch>
或者 list保存路由信息,方便在进行路由拦截,或者配置路由菜单等
const router = [
{
'path': '/index',
'component': Index
},
{
'path': '/list'',
'component': List
},
{
'path': '/detail',
'component': Detail
},
]
asyncRouter懒加载路由,并实现路由监听
我们今天讲的这种react
路由懒加载是基于import
函数路由懒加载, 众所周知 ,import
执行会返回一个Promise
作为异步加载的手段。我们可以利用这点来实现react
异步加载路由
const routerObserveQueue = [] /* 存放路由卫视钩子 */
/* 懒加载路由卫士钩子 */
export const RouterHooks = {
/* 路由组件加载之前 */
beforeRouterComponentLoad: function(callback) {
routerObserveQueue.push({
type: 'before',
callback
})
},
/* 路由组件加载之后 */
afterRouterComponentDidLoaded(callback) {
routerObserveQueue.push({
type: 'after',
callback
})
}
}
/* 路由懒加载HOC */
export default function AsyncRouter(loadRouter) {
return class Content extends React.Component {
constructor(props) {
super(props)
/* 触发每个路由加载之前钩子函数 */
this.dispatchRouterQueue('before')
}
state = {Component: null}
dispatchRouterQueue(type) {
const {history} = this.props
routerObserveQueue.forEach(item => {
if (item.type === type) item.callback(history)
})
}
componentDidMount() {
if (this.state.Component) return
loadRouter()
.then(module => module.default)
.then(Component => this.setState({Component},
() => {
/* 触发每个路由加载之后钩子函数 */
this.dispatchRouterQueue('after')
}))
}
render() {
const {Component} = this.state
return Component ? <Component {
...this.props
}
/> : null
}
}
}
asyncRouter
实际就是一个高级组件,将()=>import()
作为加载函数传进来,然后当外部Route
加载当前组件的时候,在componentDidMount
生命周期函数,加载真实的组件,并渲染组件,我们还可以写针对路由懒加载状态定制属于自己的路由监听器beforeRouterComponentLoad
和afterRouterComponentDidLoaded
,类似vue
中 watch $route
功能。接下来我们看看如何使用。
import AsyncRouter ,{ RouterHooks } from ‘./asyncRouter.js’
const { beforeRouterComponentLoad} = RouterHooks
const Index = AsyncRouter(()=>import(‘../src/page/home/index’))
const List = AsyncRouter(()=>import(‘../src/page/list’))
const Detail = AsyncRouter(()=>import(‘../src/page/detail’))
const index = () => {
useEffect(()=>{
/* 增加监听函数 */
beforeRouterComponentLoad((history)=>{
console.log(‘当前激活的路由是’,history.location.pathname)
})
},[])
return <div >
<div >
<Router >
<Meuns/>
<Switch>
<Route path={‘/index’} component={Index} ></Route>
<Route path={‘/list’} component={List} ></Route>
<Route path={‘/detail’} component={ Detail } ></Route>
<Redirect from=’/*’ to=’/index’ />
</Switch>
</Router>
</div>
</div>
}
受控性组件颗粒化 ,独立请求服务渲染单元
不是所有状态都应该放在组件的 state 中. 例如缓存数据。如果需要组件响应它的变动, 或者需要渲染到视图中的数据才应该放到 state 中。这样可以避免不必要的数据变动导致组件重新渲染.
颗粒化控制可控性组件 / 独立的请求渲染单元
1 可以避免父组件的冗余渲染 ,react
的数据驱动,依赖于 state
和 props
的改变,改变state
必然会对组件 render
函数调用,如果父组件中的子组件过于复杂,一个自组件的 state
改变,就会牵一发动全身,必然影响性能,所以如果把很多依赖请求的组件抽离出来,可以直接减少渲染次数。
2 可以优化组件自身性能,无论从class
声明的有状态组件还是fun
声明的无状态,都有一套自身优化机制,无论是用shouldupdate
还是用 hooks
中 useMemo
useCallback
,都可以根据自身情况,定制符合场景的渲条 件,使得依赖数据请求组件形成自己一个小的,适合自身的渲染环境。
3 能够和redux
,以及redux
衍生出来 redux-action
, dva
,更加契合的工作,用 connect
包裹的组件,就能通过制定好的契约,根据所需求的数据更新,而更新自身,而把这种模式用在这种小的,需要数据驱动的组件上,就会起到物尽其用的效果。
shouldComponentUpdate ,PureComponent 和 React.memo ,immetable.js 助力性能调优
我们用react-redux
来简单举一个例子,如下所示 数据都已经被 immetable.js
处理。
import { is } from 'immutable'
const GoodItems = connect(state =>
({ GoodItems: filter(state.getIn(['Items', 'payload', 'list']), state.getIn(['customItems', 'payload', 'list'])) || Immutable.List(), })
/* 此处省略很多代码~~~~~~ */
)(memo(({ Items, dispatch, setSeivceId }) => {
/* */
}, (pre, next) => is(pre.Items, next.Items)))
规范写法,合理处理细节问题
绑定事件尽量不要使用箭头函数
react
更新来大部分情况自于props
的改变(被动渲染),和state
改变(主动渲染)。当我们给未加任何更新限定条件子组件绑定事件的时候,或者是PureComponent
纯组件, 如果我们箭头函数使用的话。
<ChildComponent handerClick={()=>{ console.log(666) }} />
每次渲染时都会创建一个新的事件处理器,这会导致 ChildComponent
每次都会被渲染。
解决问题
function index(){
const handerClick1 = useMemo(()=>()=>{
console.log(777)
},[]) /* [] 存在当前 handerClick1 的依赖项*/
const handerClick = useCallback(()=>{ console.log(666) },[]) /* [] 存在当前 handerClick 的依赖项*/
return <div>
<ChildComponent handerClick={ handerClick } />
<div onClick={ handerClick1 } >hello,world</div>
</div>
}
循环正确使用key
错误用法一:用index做key
错误用法二:用index拼接其他的字段
正确用法:用唯一id作为key
无状态组件hooks-useMemo
避免重复声明
错误用法
function Index(){
const [ number , setNumber ] = useState(0)
const handerClick1 = ()=>{
/* 一些操作 */
}
const handerClick2 = ()=>{
/* 一些操作 */
}
const handerClick3 = ()=>{
/* 一些操作 */
}
return <div>
<a onClick={ handerClick1 } >点我有惊喜1</a>
<a onClick={ handerClick2 } >点我有惊喜2</a>
<a onClick={ handerClick3 } >点我有惊喜3</a>
<button onClick={ ()=> setNumber(number+1) } > 点击 { number } </button>
</div>
}
正确写法
function Index(){
const [ number , setNumber ] = useState(0)
const [ handerClick1 , handerClick2 ,handerClick3] = useMemo(()=>{
const fn1 = ()=>{
/* 一些操作 */
}
const fn2 = ()=>{
/* 一些操作 */
}
const fn3= ()=>{
/* 一些操作 */
}
return [fn1 , fn2 ,fn3]
},[]) /* 只有当数据里面的依赖项,发生改变的时候,才会重新声明函数。 */
return <div>
<a onClick={ handerClick1 } >点我有惊喜1</a>
<a onClick={ handerClick2 } >点我有惊喜2</a>
<a onClick={ handerClick3 } >点我有惊喜3</a>
<button onClick={ ()=> setNumber(number+1) } > 点击 { number } </button>
</div>
}
懒加载 Suspense 和 lazy
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function demo () {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
<LazyComponent1 /> //Suspense 可以包裹多个懒加载组件
</Suspense>
</div>
)
}
多种方式避免重复渲染
批量更新
class index extends React.Component{
constructor(prop){
super(prop)
this.state = {
a:1,
b:2,
c:3,
}
}
handerClick=()=>{
const { a,b,c } :any = this.state
this.setState({ a:a+1 })
this.setState({ b:b+1 })
this.setState({ c:c+1 })
}
render= () => <div onClick={this.handerClick} />
}
点击事件发生之后,会触发三次 setState
,但是不会渲染三次,因为有一个批量更新batchUpdate
批量更新的概念。三次setState
最后被合成类似如下样子
const [ a , setA ] = useState(1)
const [ b , setB ] = useState({})
const [ c , setC ] = useState(1)
const handerClick = () => {
setB( { ...b } )
setC( c+1 )
setA( a+1 )
}