内容简介:preact作为备胎,但是具有体积小,diff算法优化过的特点,简单活动页用上它是不错的选择。但是考虑到react令人兴奋的新特性,preact并没有按时更新去完全支持它,更严重的是一些babel插件、一些库配合preact会有问题。所以,还是不得不迁移了。如何迁移?package.json直接修改版本,删掉preact,重装,完事!too young
0. 前言
preact作为备胎,但是具有体积小,diff算法优化过的特点,简单活动页用上它是不错的选择。但是考虑到react令人兴奋的新特性,preact并没有按时更新去完全支持它,更严重的是一些babel插件、一些库配合preact会有问题。所以,还是不得不迁移了。
如何迁移?package.json直接修改版本,删掉preact,重装,完事!
too young
1. 从alias改起
首先,一般是 这样子 接入preact的,使得我们代码里面毫无感觉我们用的是preact。在webpack的alias里面配置:
alias: { react: 'preact-compat', 'react-dom': 'preact-compat' },
所以,第一步先把这个去掉
2. 语法上
- preact的元素数组可以不写key,切换回来必然警告很多,需要把key补上
render() { return ( [ <div key="container">2</div>, <div key="more">1</div>, ] ); }
- 元素的style可以写字符串,转回react是报错,导致页面白屏
<div style={`background: url(${this.props.h5img});`} />
- will这些不安全的生命周期,需要手动修改
- state必须初始化,不能直接想有this.state.xx就有。必须保证后面用到this.state之前,对state有初始化,否则是null
3. preact相关的router迁移回react生态
首先,import的preact-router得换成react-router。然后,将对应的语法和生态迁移到react相关的。
preact-router:
<Router history={history}> <Main path="/" history={history} /> <GetGiftForm path="/get_gift_form" history={history} /> <Join path="/join" history={history} /> <NewUser path="/new_user" history={history} /> </Router>
react-router:
<Router history={history}> <div> <Route exact path="/" history={history} component={Main} /> <Route path="/get_gift_form" history={history} component={GetGiftForm} /> <Route path="/join" history={history} component={Join} /> <Route path="/new_user" history={history} component={NewUser} /> </div> </Router>
preact-router有一个route方法,就是直接将路由push或者replace的,而react-router是没有这个方法的。实际上底层就是封装history路由加上内部的setstate:
import { route } from 'preact-router'; route('/a');
问题来了,如果没有这个方法,想用脚本跳转路由怎么办?直接history上改,只能改地址栏的url显示但不能更新组件以及内部状态。所以我们只能找和react-router配合起来用的相关的库。
观察一下,都用到了history属性,传入一个history,这个是用了一个history的库创建的,所以我们尽可能的让它接入两种路由得到一样的效果:
import createHashHistory from 'history/createHashHistory'; const history = createHashHistory();
打印了history,发现它有push、replace属性,大概也猜到应该就是像route的效果的,一验证发现可行:
history.push('/a');
另外,还有preact-router的路由更新监听是这样的:
<Router history={history} onChange={this.handleRoute}> ...... </Router>
切换到react的话,没有这个方法。于是我们继续找history,发现有一个listen属性,看名字是一个监听函数,也就是可以实现路由更新监听的:
history.listen((location) => { ... });
4. 异步路由
用preact-router的时候,有些组件是异步的:
<Router history={history}> { trackRoute() } </Router>
trackRoute函数组件:
import React from 'react'; import AsyncRoute from 'preact-async-route'; export default () => { return ( <AsyncRoute path="/track" getComponent={async () => { return import('./comp' /* webpackChunkName: 'async_track' */); }} /> ); };
效果就是,动态import,代码分割。react也有一个类似的, react-async-router ,但是用法和我们的之前的 preact-async-route 差得远而且不能优雅接入。既然是16.6.7了,我们可以试一下新特性:lazy+suspence
import React, { lazy, Suspense } from 'react'; const Comp = lazy(() => import('./comp' /* webpackChunkName: 'async_track' */)); function TrackRoute() { return ( <Suspense fallback={<div />}> <Comp /> </Suspense> ); } export default TrackRoute;
5. 内部实现原理不一样的兼容
有一个页面是这样的:
// Main.jsx render() { return ( <div> <Page1 /> <Page2 /> <Page3 /> ... </div> ); }
除了page1是原来就在的,其他每一个Pagex组件,返回Page组件,在Page内部,当页码是当前页返回对应的元素,否则返回空:
// Pagex render() { return ( <Page /> ); } // Page render() { return currentPage === page ? <somedom> : null }
这里,我们可以猜一下,Main是最大的组件,内部状态页码在切换,所有的Pagex组件跟着更新,做出对应的变化。Pagex的更新,走的是didupdate。
实际上,preact的是第一个内部是Page实现的Pagex组件会unmount然后重新didmount。这里是Page2先卸载再挂载,交换位置page1直接到page3的话也是page3先卸载再挂载。一些动画操作就放在了didmount,之前都是这样做的,但大家没有发现是什么问题,因为看见是这样,开发起来没毛病,又没有bug,就不太在意。切换回react,发现动画不生效,才发现因为内部渲染机制不一样导致的。所以我们把函数的调用放在didupdate里面,并且加上执行过一次的标记判断。
6. 减少无必要的函数执行
getSnapshotBeforeUpdate
做一个像qq聊天起跑那样的东西,头和脚固定,中间无限长。这里要求视觉给3个图,头、脚、中间1px高的图。如果内容不满屏,不显示脚不能滚动,如果大于1屏显示脚。
这里当然少不了原生dom操作了,需要判断高度,有没有大于1屏,要不要overflow:hidden:
componentDidUpdate() { if (this.state.hasFooter) { return; } const last = document.querySelector('.act-card:last-of-type'); if (last) { const { top } = last.getBoundingClientRect(); if (SCREEN_HEIGHT - top < MAX_BOTTOM_DISTANCE) { setTimeout(() => { this.setState({ hasFooter: true }); // eslint:不能在didupdate里面setstate }); document.body.style.overflow = 'auto'; } else { document.body.style.overflow = 'hidden'; } } }
这里需要执行两次下面一大块逻辑,第二次是无必要的,我们可以利用getSnapshotBeforeUpdate生命周期配合didupdate使用:
getSnapshotBeforeUpdate(_, prevstate) { if (!prevstate.hasFooter && prevstate.actCards.length) { return true; } return null; } componentDidUpdate(_, __, snapshot) { if (!snapshot) { return; } ...... }
memo
可以说函数式组件的purecomponent,而且第二个参数能传入第二个类似shouldComponentUpdate的函数进行比较。既然能自定义化,那么对比于purecomponent的自带对象浅比较就是更加的灵活了,比如:
import React, { memo } from 'react'; export default memo((props) => { const isNoAct = !props.actCards.length || props.loading; return ( <section className="body-container"> <div> { !isNoAct ? props.actCards.map((actCard, index) => ( <div key={index} > ...... </div> )) : ( <div className="no"> 暂无 </div> ) } </div> {props.hasFooter && <div className="body-footer" src={footer} alt="err" />} </section> ); }, (prevprops, nextprops) => { // 少渲染一次,一开始actcards什么都没有,我们不比较actcards数组 if (prevprops.hasFooter !== nextprops.hasFooter || prevprops.loading !== nextprops.loading) { return false; } return true; });
这里我们就少了一次从actcards数组的 undefined
到 []
的过程的比较,而这时候一直是loading状态,没有更新的意义。如果这里return之前又是有像前面那个聊天气泡那种效果需要dom操作的,那就伤性能了。这里我列举出来的只是把代码删减过的简单结果,实际上开发的时候逻辑是远远比这demo复杂的
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Introduction to Programming in Java
Robert Sedgewick、Kevin Wayne / Addison-Wesley / 2007-7-27 / USD 89.00
By emphasizing the application of computer programming not only in success stories in the software industry but also in familiar scenarios in physical and biological science, engineering, and appli......一起来看看 《Introduction to Programming in Java》 这本书的介绍吧!