Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

搜狗社区搜索Preact迁移指南 #13

Open
mopduan opened this issue Sep 19, 2017 · 0 comments
Open

搜狗社区搜索Preact迁移指南 #13

mopduan opened this issue Sep 19, 2017 · 0 comments

Comments

@mopduan
Copy link
Owner

mopduan commented Sep 19, 2017

背景

近期团队对部门系列产品进行一系列的性能优化,发现对于搜狗问问、搜狗指南等产品来说(搜狗百科用的是Vue.js),React显得过“重”,页面domReady时间较长;此外,由于Facebook将React协议更改为臭名昭著的“BSD+许可”协议,考虑公司利益,同时考虑到React组件化、优雅的jsx语法等诸多优点和项目、团队技术的迁移成本;因此寻找类React的轻量级解决方案逐渐提上日程。

Preact 特点

在选型的时候,主要基于以下几个考量:

  • 开源社区有较多star
  • 较好的性能和兼容性
  • api跟React接近
  • 丰富的配套框架,比如redux和router的使用

调研发现,以上几点Preact都能够很好的满足,因此最终选定为团队的类React轻量化框架进行使用和研究;

开源社区有较多star

相比于react-liteinferno, Virtual-DOM等类React轻量级框架,Preact star数量排名第一,国内成熟的产品如腾讯QQ、花样直播等都在使用。

较好的性能和兼容性

Preact在性能方面也表现不俗。bundle在压缩后大概只有3kb,体积比React小很多,大大节省了下载和加载时间。
类React框架包大小对比:

framework version minimized size gzip size
React 0.15.6 149.74kb 46.46kb
React-lite 0.15.3 27.8kb 10.6kb
Preact 8.2.5 9.05kb 3.36kb
inferno 4.0.0-alpha1 47.94kb 8.6kb
Virtual Dom 2.1.1 45.4kb 11.8kb

在渲染性能方面,参考JS WEB FRAMEWORKS BENCHMARK系列测评文章,发现Preact在创建、更新、删除节点等操作中,有良好的表现。
首次性能测试:
results2-1024x351

此外,preact能兼容目前的主流浏览器,并且在添加polyfill的情况下,能够兼容在IE8

Api与React接近

Preact常用api基本跟React一致,这使得对React熟悉的开发者,几乎没有上手的难度,React与Preact的异同可参考官网Differences to React;如果想使用一些缺失的React Api,可以使用preact-compat,在Webpack上的external属性上作如下替换即可:

{
    resolve: {
        alias: {
            'react': 'preact-compat',
            'react-dom': 'preact-compat'
        }
    }
}

丰富的配套框架

与React配套框架react-redux和react-router相似,Preact也有提供preact-reduxpreact-router,甚至还有帮助Preact做同构直出的preact-render-to-string

Preact VS React

Preact致力于保持轻量专注,去掉React一些较占体积但“收益”较少的特性,并增加React社区呼声较高的新特性。

Preact包含的特性

  • ES6类
  • 高阶组件: 组件在render中返回其他组件
  • 无状态纯函数式组件
  • context
  • 函数refs
  • 虚拟dom比较
  • h():更为通用的react.createElement版本

新增特性

Preact 实际上添加了几个更为便捷的特性,灵感源于 React 的社区

  • props和state可以传进 render() 作为参数
  • Linked State: 输入框值、状态和state双向绑定
  • 可以使用标准的HTML属性;比如class和for
  • 批量 DOM 更新,setTimeout(1) 进行函数节流 使用 (也可以使用 requestAnimationFrame)
  • 组件和元素循环使用 / 存入池中

缺少特性

  • PropType:并非所有人使用 PropTypes,所以它们并非 preact 的核心
  • Children: 在 Preact 中并非必要, 因为 props.children 总是一个数组
  • Synthetic Events: Preact不需要过度考虑不同浏览器对事件处理的异同,所以也并没有做过度封装

其他区别

Preact与React不同,组件渲染render方法为preact库的核心方法,渲染过程不需要引入其他模块;API定义如下:

render(component, containerNode, [replaceNode])

Preact默认追加到containerNode这个DOM节点上,返回一个对渲染的DOM节点的引用。
如果提供了可选的DOM节点参数 replaceNode 并且是 containerNode 的子节点,Preact将使用它的diff算法来更新或者替换该元素节点。否则,Preact将把渲染的元素添加到 containerNode 上。
注意: 这个将来的版本可能会有小的调整,可能会改成默认替换。

从React迁移到Preact

目前问问和指南已经完成Preact迁移,迁移工作主要包含wenke工具修改和应用前端代码修改。

wenke工具修改

为了使wenke工具支持构建Preact应用,需要package.json中添加Preact库的版本依赖和Babel预编译支持,目前主站和指南均引用版本8.2.5,preset请使用babel-preset-preact

同时,preact也支持react devTools调试,只需要在入口文件头部判断当前是否为dev环境,如果是,添加如下代码即可

require('preact/devtools');

此外,在用wenke构建生产包过程中,为了避免将preact打包而导致bundle体积较大,可以在webpack的externals属性中添加preact,并在代码中从CDN中引入preact压缩包。

应用前端代码修改

首先,下载Preact开发包和压缩包并上传到CDN,在代码中引入preact,方法如下:

<script src="//cache.soso.com/wenwen/deploy/js/preact/8.2.5/preact.dev.js" data-prod="//cache.soso.com/wenwen/deploy/js/preact/8.2.5/preact.min.js"></script>

在现有的React应用中,有两种途径把 React 替换成 Preact:

  • 安装 preact-compat
  • 把 React 的入口替换为 Preact,并解决代码冲突

基于Preact的api几乎跟React的api一致,React应用的迁移只需要很少甚至不用作改动;因此主站、指南采用途径二完成迁移,也是最理想的迁移方法。

步骤如下:

  1. 使用preact替换react、react-dom和react-with-addons
  2. 使用函数refs替换字符串refs,因为preact不支持字符串refs
  3. 组件创建方法全部改用ES6 Class形式,因为preact不支持createClass接口
  4. 在textarea,input和select封装的controlled Component中,去掉defaultChecked和defaultValue预设值;可以通过preact中LinkedState来解决
  5. 把 ReactDOM.render() 转换成preact的 render()
  6. 去掉ReactDOM相关方法调用,比如ReactDOM.findDOMNode()
  7. 去掉defaultProps
  8. 将代码中使用React.createElement()方法创建虚拟dom转换成preact中的h()方法;

遇到的那些坑

1、h() 方法

import {h,  Component} from 'preact';

注意:在preact 组件中不会显示的去用h()方法,但是在打包处理的时候会用到,所以代码检测工具会提示h方法从未被调用,但不能将其删除,如果删除掉,打包后的代码会报错。

2、refs

ref={(content)=>{this.container = content;}}

注意:preact中ref只支持回调函数的方式;如果想继续使用字符串refs,可以在项目中引入preact-compat兼容包;
此外,强烈建议大家使用函数式refs替代字符串refs,根据react官网,字符串refs在react未来版本中也将摒弃。这里可以发现,从preact很多特性中可以看出react在未来较长一段时间的演化方向;

3、img的宽高

<div class="user-thumb-box">
    <a href="#" target="_blank" class="user-thumb getUserCard" data-uid="34755361">
        <img src="..." width="100%" height="100%" alt="头像">
    </a>
</div>

注意: render方法里面,以上结构中的百分比会被改成0,因此不能使用百分比设置img宽高

4、render组件问题

render(<MessageBox />, document.getElementById("userNotice"))

注意: 如果没有传入第三个参数,默认会保留userNotice容器里面原有的内容,并将MessageBox追加到userNotice子节点内容中;如果想要重新渲染userNotice,将首次render返回值(userNotice虚拟dom)保存到变量root,并在再次渲染中,将root传入第三个参数中即可。

5、componentWillUnmount 生命周期函数触发方式

preact中没有显式的ReactDOM.unmountComponentAtNode方法调用,需要同过以下方式触发销毁生命周期函数:

//preact 中的render方法多次调用是向渲染元素追加,如果想替换,需要传入第三个参数。
function unmountComponentAtNode(container, child) {
    return render(<EmptyComponent />, container, child);
    function EmptyComponent() { return null; }
}
class Test extends Component{
	constructor(){
		super()
	}
	componentWillUnmount(){
		console.log('componentWillUnmount');
	}
	render(props,state){
		return <div>测试触发销毁生命周期函数的方法 {props.name}</div>
	}
}
var container = document.getElementById('#container');
var base = render(<Test name="lifei"/>,container);
// render(null,container,base)//效果如同下面封装的方法,在同一个容器中,传入第三个参数,替换原来渲染的组件为空,或者为别的组件,均会触发销毁方法
unmountComponentAtNode(container,base)

6、使用onInput回调函数代替onChange监听输入值的变化

export default class Example extends Component {
    state = {  text: '' };
    setText = e => {
        this.setState({ text: e.target.value });
    };
    render({ }, { text }) {
        return (
            <form >
                <input value={text} onInput={this.setText} />
                <button type="submit">Add</button>
            </form>
        );
    }
}

注意:在上述受控组件中,React建议使用onChange方式监听输入值的变化并设置state;然而在preact中,onChange回调函数常常无法准确触发,官方推荐使用onInput回调方法代替onChange方法;当然,你也可以继续使用onChange方法,但需要引入preact-compat兼容包。

7、避免使用相同key的数组item放在同一个组件中

class App extends Component {
	render() {
	   return (
		<div class="App">
			{ [1, 2, 3].map(() => (<span key={ 0 }>Hello</span>)) }
			<button onClick={ () => this.forceUpdate() }>
				Rerender
			</button>
		</div>
	 );
    }
}

注意:在react当中,我们知道在数组渲染时给每个item增加key属性,key的唯一性可以在重新渲染时提高性能,但相同key也并不会产生异常现象;然而在preact组件中,如果存在相同key数组元素,不管是在同一个数组中或者另外一个数组中,组件在更新时将重新渲染,返回新的数组组件append到container中。
解决方案: 使用数组index或Math.random()保证每个item中key的唯一性

再一次感谢您花费时间阅读这篇文章!祝您在这里记录、阅读、分享愉快!

转载请注明出处。

如果这篇文章对您有帮助,欢迎打赏:)

欢迎打赏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant