前言
距离我进新公司也有一个多月,这一个月的事件使用react写了一个项目,期间断断续续重构了两三次,目前已经完成第一阶段测试,也总结分享一些使用react的一些坑。
react
先啰嗦几句讲一下react原理,新人可以认真看下,老鸟可跳过。
react并没有像其他如vue,ng一样采用MVVM模式,所谓MVVM模式,狭义来说就是将模板与数据绑定在一起,当数据发生改变时,模板自动更新,这是中间的VM,最左边的M可以理解为我们看到的页面,而最右边的M可以理解为原始数据(例如数据库数据)。
其实要知道,这些框架模式归根结底的目的希望使代码更容易开发和维护。当你写一个小页面你不觉得什么,但是当你的页面越来越庞大,越来越复杂,开发人员走了一批又一批的时候,你就会明白了。而如何解决,现在比较公认的理念一个是组件化,将页面拆分成一个个组件,其实拆分成组件的目的并不全是为了复用,我觉得更多是为了维护;一个就是希望让应用层的编程能更专注于业务逻辑,那么这些框架都去做了处理,减少了大量DOM操作,让业务开发更加专注。
那么我们看看react采用的是什么原理。其实react采用的方法简单粗暴:它并没有对模板做数据绑定,而是每当数据变化时,就重新渲染模板。这有一个很大的好处就是每当数据变化时,对页面来说只有一次IO操作,而单纯的双向绑定更新DOM会有很多次;但有一个问题,如果只改变了一个dom的数据,整个模板都会重新渲染。那react解决的方法是每当数据改变时,就进行对比。
那么该如何对比呢,最简单的方法是每当数据改变,就去页面获取相应的DOM信息,然后与现在的DOM信息做比较。这个方法有个致命的缺点就是每次有DOM改变,就会有许多读取操作,IO操作太多,很影响性能。那么可以通过空间换时间的方法,将DOM信息保存起来,就避免了去页面获取信息的IO操作。那么我们看看一个真实DOM有多少数据
虚拟DOM
如果我们把所有原生DOM缓存起来进行比较显然内存会爆炸,而我们所需要的仅仅是几个为数不多的状态信息(例如style啊这些),这时虚拟DOM应运而生,如果说原生DOM是一块猪肉,那么虚拟DOM就是这块猪肉中多精肉,他剔除了那些对我们来说没有太多意义相反还占内存的状态信息,而只将我们所需要的内容留了下来。那虚拟DOM该如何比较呢,就涉及到了虚拟DOM的diff算法。
diff算法
简单来说两个模板就像两棵树一样,传统的树对比的时间复杂度是O^3,也就是说要整棵树遍历三次,那react根据DOM的特点:跨层级的操作较少。什么叫跨层级,举个例子一个组件有三个层级关系(嵌套三层),把第二层的div放到第三层就属于跨层级操作。这种操作其实还是比较少的,react官方也不推荐这么写。那么利用这个特点,react只diff同层级的DOM。这里涉及以下几种情况
- tag变化(标签变化)
- 属性变化
第二种没什么好说,就是进行同层级对比。这里详细说一下第一种变化,react的方法是当前层级往下全部删除替换,简单暴力。在业务开发来看,你同层级类型都不同了,比如div变成了input,那么你的子组件相同的几率也较小,因此不如整个替换简单暴力。同时这也说明为什么列表需要key属性,因为列表很多的删除操作对于react来说是不知道的,它需要一个key来了解到底谁是谁。
踩过的一些坑
state
作为前端,拿到原型第一件事就是要与你的产品充分沟通,评估该项目是否需要引入redux,那么如何确定需要引入redux呢,这边有几点:
- 页面之间组件之间通信较多,且是跨层级的;
- 页面的交互逻辑较复杂,且经常引起多处组件变化。
再然后就是对state的设计。对于后台的返回的数据,你并不知道到底哪些是有用的,哪些可能现在没用以后有用,因此我的建议是对于每个api都将它存在一个object里。那么组件该如何去获取,首先不能全部通过顶部container获取依次往下传,也不能粒度很细的去一个个connect,我的建议是对于一个object,可以用一个container去connect,粒度把握主要看你的业务需求。
还有一个要不要全局都使用redux。我的观点是否定的,我认为对于局部一个组件内的状态完全可以通过setState来满足。
图片处理
首先将图片做一些划分。例如以500k作为分界,小于500k为小图片,否则是大图片。对于小图片,我们需要做如下判断:
- 页面是否重复使用?是就用雪碧图,否则转base64.
- 对于大图片,可以进行压缩后使用。
base64可以用webpack的url-loader替换。
举个例子
require('url?limit=250!./xxx.png')
//这里就会使用url-loader,假如图片小于250,就会转为base64
移动端适配
对于适配,我所知比较好的方法是利用rem作为单位,将页面宽度等分成10个rem,根据页面动态的用js去改变font-size,达到适配不同浏览器的目的。例如爱疯宽320px,那么font-size设置为32px。那么10rem就是等于整个屏幕的宽度。但是有一个特例就是高清屏,一般高清屏的物理像素是实际像素的2倍,那么当你想显示一个宽度为1的边框时,在普通屏幕是1px,在高清屏可以有0.5px(问题是很多浏览器不支持,为将0.5px认为是0)。虽然都使用1px在两者屏幕上实际上是一样的,但是高清屏里的1px在射鸡师眼里是无法达到他对于1的要求的。于是就有一些解决方案,比较简单是是使用transform:scale(0.5)。那么还有一种解决方案就是阿里的移动端解决方案,原理是将页面整体scale缩小,然后放大font-size,来保证rem为单位的布局不变,但是px为单位的会被缩小。
性能提升
首先先确定需求,确实有这个需求的时候再谈。
懒加载
webpack其实会帮我们做第三方依赖的懒加载处理,那么针对react,我们可以通过现成的库来实现懒加载react-lazyload,或者使用webpack现成的
require.ensure([],()=>{
require('public.js')
})
来实现。
shouldComponentUpdate
其实这个钩子可以极大的帮助我们去提升性能,由于它的存在,我们可以自己判断哪些是我们组件需要的state,哪些是不需要的,那么这就可以阻止react进行不必要diff。但是有一个问题就是对于对象我们很难去判断他们是否相等,那么可以通过immutableJs的fromJS和is方法来解决这个问题。其实immutableJs的好处远不止于此,目前我也尚在填坑中。
使用不可变数据,可以更好的达到函数式编程,不仅利于单元测试,也更有利于后期维护整个大的state。因为他的不可变特点,我们不会在不经意见不小心改变了state,而引起不必要的问题。
创建组件的痛点
为了使组件中的css作用域相互独立,一般采用Css Module
,那么为了使组件看上去更像组件,并且易于后期维护,一般我们会这样结构化组件文件:
那么对于每一个初始的jsx,我们大多会这样初始化
import s from './App.scss'
import{Component} from 'react';//得到组件方法
class App extends Component{
render(){
return (
<div className={s.container}>
</div>
)
}
}
export default App;
不难发现,对于每一个组件,我都需要去手动的创建这些文件和文件夹,而这些操作其实是重复且无意义的劳动,于是我造了一个小轮子,一个命令行小工具,来解决这个痛点。
react-component-maker,欢迎猛戳点star!
结语
困了,本宝宝要睡觉了,还有的内容下次再说吧,再见。