系列文章
React系列(一)-- 2013起源 OSCON - React Architecture by vjeux
React系列(四)--- virtualdom diff算法实现分析
前言
我们先不讲什么语法原理,先根据API效果强行模拟语法使用,实现一个简易版的React.
下面代码跟React源码没有关系,只是单纯模拟思路实现
render
第一步我们先用类创建一个元素返回,并且绑定点击事件,代码如下,可以正常看到一个按钮出现了.
class AddButton {
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 输出实例
render() {
this.dom = this.createDOM(`<button>0</button>`);
this.dom.addEventListener("click", () => console.log("click"), false);
return this.dom;
}
}
// 插入页面
document.body.appendChild(new AddButton().render());
state && setState
实现类状态和修改状态方法
class AddButton {
// 内置状态
constructor() {
this.state = { num: 0 };
}
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 唯一修改状态更新视图
setState(state) {
this.state = state;
this.dom = this.render();
}
// 修改数据
handleAdd() {
const num = this.state.num + 1;
this.setState({
num: num
});
}
// 输出实例
render() {
this.dom = this.createDOM(`<button id="btn">${this.state.num}</button>`);
this.dom.addEventListener("click", () => this.handleAdd(), false);
return this.dom;
}
}
// 插入页面
document.body.appendChild(new AddButton().render());
渲染之后看到this.dom
输出已经发现改变了,但是视图并没有渲染,那是因为这是结尾一次性插入,下面就渲染视图这块往下走
重新渲染
我们现在把插入数据的操作内置到class里面,新增一个方法插入新元素移除旧元素.
class AddButton {
// 内置状态
constructor() {
this.state = { num: 0 };
}
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 只是一个替换元素的方法
changeDom() {
const oDom = this.dom;
this.dom = this.render();
document.body.insertBefore(this.dom, oDom);
document.body.removeChild(oDom);
}
// 唯一修改状态更新视图
setState(state) {
this.state = state;
this.changeDom();
}
// 修改数据
handleAdd() {
const num = this.state.num + 1;
this.setState({
num: num
});
}
// 输出实例
render() {
this.dom = this.createDOM(`<button id="btn">${this.state.num}</button>`);
this.dom.addEventListener("click", () => this.handleAdd(), false);
return this.dom;
}
}
// 插入页面
document.body.appendChild(new AddButton().render());
现在效果虽然实现,但是还是得开头手动把元素插入视图
抽取公用类
我们先将一些共有方法提取到一个单独类,另外补全一下props属性传参
class React {
// 内置状态
constructor(props = {}) {
// 实例
this.wrapper = null
// 状态
this.state = {}
// 属性
this.props = {}
}
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 只是一个替换元素的方法
changeDom() {
const oDom = this.dom;
this.dom = this._render();
this.wrapper.insertBefore(this.dom, oDom);
this.wrapper.removeChild(oDom);
}
// 唯一修改状态更新视图
setState(state) {
this.state = state;
this.changeDom();
}
// 输出实例
_render(wrapper) {
if (wrapper) this.wrapper = wrapper;
this.dom = this.createDOM(this.render());
this.dom.addEventListener("click", () => console.log('添加自定义事件'), false);
return this.dom;
}
}
然后组件只需要直接继承Component
然后处理自己逻辑即可
class AddButton extends React {
constructor() {
super();
this.state = { num: 0 };
}
handleAdd() {
const num = this.state.num + 1;
this.setState({
num: num
});
}
render() {
return `<button id="btn">${this.state.num}</button>`;
}
}
上面做了几件事:
- 抽取通用的逻辑到React类
- 自定义组件AddButton
还有一个问题是点击事件暂时还是耦合进Component
里面,下一章节实现
添加自定义事件
因为事件是由组件自定义的,所以我们思路是在组件实例定义好之后,通用类绑定事件
class React {
// 内置状态
constructor(props = {}) {
// 实例
this.wrapper = null
// 状态
this.state = {}
// 属性
this.props = {}
// 事件
this.event = {}
}
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 只是一个替换元素的方法
changeDom() {
const oDom = this.dom;
this.dom = this._render();
this.wrapper.insertBefore(this.dom, oDom);
this.wrapper.removeChild(oDom);
}
// 唯一修改状态更新视图
setState(state) {
this.state = state;
this.changeDom();
}
// 初始化事件
initEvent() {
const events = Object.keys(this.event)
events.forEach(key => {
this.dom.addEventListener(key, this.event[key].bind(this), false);
})
}
// 输出实例
_render(wrapper) {
if (wrapper) this.wrapper = wrapper;
this.dom = this.createDOM(this.render());
// 需要创建实例后才能初始化
this.dom && this.initEvent()
return this.dom;
}
}
同时组件代码也需要相对应的修改
class AddButton extends React {
constructor() {
super();
this.state = { num: 0 };
this.event = {
click: this.handleAdd
}
}
handleAdd() {
const num = this.state.num + 1;
this.setState({
num: num
});
}
render() {
return `<button id="btn">${this.state.num}</button>`;
}
}
ReactDom.render
大家都知道React会提供这么一个方法将组件插入一个指定元素,我们直接模拟
const ReactDom = {
// 指定元素内插入组件实例
render(component, wrapper) {
wrapper.appendChild(component._render(wrapper));
}
};
最终运行代码
我们上面已经实现了几个功能:
- 负责创建元素的React类,包括单向数据流,自定义状态,事件,替换元素等
- 负责挂载的ReactDom类
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
</head>
<body>
<div id="root"></div>
<script>
class React {
// 内置状态
constructor(props = {}) {
// 实例
this.wrapper = null
// 状态
this.state = {}
// 属性
this.props = {}
// 事件
this.event = {}
}
// 创建Dom
createDOM(domString) {
const div = document.createElement("div");
div.innerHTML = domString;
return div;
}
// 只是一个替换元素的方法
changeDom() {
const oDom = this.dom;
this.dom = this._render();
this.wrapper.insertBefore(this.dom, oDom);
this.wrapper.removeChild(oDom);
}
// 唯一修改状态更新视图
setState(state) {
this.state = state;
this.changeDom();
}
// 初始化事件
initEvent() {
const events = Object.keys(this.event)
events.forEach(key => {
this.dom.addEventListener(key, this.event[key].bind(this), false);
})
}
// 输出实例
_render(wrapper) {
if (wrapper) this.wrapper = wrapper;
this.dom = this.createDOM(this.render());
// 需要创建实例后才能初始化
this.dom && this.initEvent()
return this.dom;
}
}
const ReactDom = {
// 指定元素内插入组件实例
render(component, wrapper) {
wrapper.appendChild(component._render(wrapper));
}
};
class AddButton extends React {
constructor() {
super();
this.state = { num: 0 };
this.event = {
click: this.handleAdd
}
}
handleAdd() {
const num = this.state.num + 1;
this.setState({
num: num
});
}
render() {
return `<button id="btn">${this.state.num}</button>`;
}
}
ReactDom.render(new AddButton(), document.getElementById("root"));
</script>
</body>
</html>
至此,抛开实际思路不说,我们已经简单模拟出来React的基本语法实现了.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。