开篇
如今前端开发框架里,React已经是前端开发中最受欢迎的框架之一,对于前端的重要性不言而喻。从今天开始我将用几个篇幅来实现一个简单的React框架, 带大家一起来了解React的基本机制。
React
用过React的童鞋们应该都知道React的 JSX 语法,其实 JSX 的写法就类似于HTML标签,经过解析后,实际上执行的是React.createElement,如下:
// jsx语法
function render() {
return <div>hello react</div>;
}
// 经过解析后,实际上执行的是
function render() {
return React.createElement((type: 'div'), {}, 'hello react'); // 返回一个js对象:{type: 'div',attrs: {},children: ['hello react']}
}
因此我们可以开始模拟第一个函数,React.createElement:
// 创建一个React对象,后面导出
const React = {};
/*
* createElement方法
* 3个参数type,attrs,children(多个,所以会返回数组)
*
*/
React.createElement = function (type, attrs, ...children) {
return {
type,
attrs,
children,
};
};
export default React;
ReactDOM
以上就是React.createElement的基本核心代码,接下来我们来实现另外一个对象 --- ReactDOM,细节我就不多说了,就是来实现创建dom节点的方法,直接看代码:
// 创建一个ReactDOM对象,后面导出
const ReactDOM = {};
/*
* render方法
* 2个参数:vnode 就是createElement返回的对象;container就是dom节点,如:document.getELementById('app')
*
*/
ReactDOM.render = function (vnode, container) {};
export default ReactDOM;
定义好了render方法后,我们来具体分析下他的参数,vnode 和 container,我们说了vnode实际上就是React.createElement返回的对象,container是dom节点,我们来修改一下ReactDOM.render方法:
// 创建一个ReactDOM对象,后面导出
const ReactDOM = {};
/*
* render方法
* 2个参数:vnode 就是createElement返回的对象;container就是dom节点,如:document.getELementById('app')
*
*/
ReactDOM.render = function (vnode, container) {
let element;
// 判断传入的vnode是不是string,如果是直接创建文本节点
if (typeof vnode === 'string') {
element = document.createTextNode(vnode);
} else {
// 如果是vnode对象,则直接创建html节点
element = document.createElement(vnode.type);
}
// 将节点元素插入html中
container.appendChild(element);
};
export default ReactDOM;
至此一个最基本的 React 库就可以执行了,我们可以先创建一个index.html来测试下: index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
<!-- react.js 包含React和ReactDOM两个对象 -->
<script src="./react.js"></script>
<!-- index.js 引入并执行我们的React方法 -->
<script src="./index.js"></script>
</html>
我们来看下index.js文件: index.js
// JSX会被解析并执行React.createElement,返回一个vnode对象
const element = <div>a single react</div>;
// element实际上执行的是
// const element = React.createElement(type: 'div', {}, ['a single react'])
ReactDOM.render(element, document.getElementById('root'));
把html文件在浏览器上运行下就可以看到 a single react 字样,说明React运行成功。我们再改动一下index.js:
// JSX会被解析并执行React.createElement,返回一个vnode对象
// 多嵌套一层span
const element = (
<div>
<span>a single react</span>
</div>
);
// element实际上执行的是
// const element = React.createElement(type: 'div', {}, [React.createElement(type: 'span',{}, ['a single react'])])
ReactDOM.render(element, document.getElementById('root'));
递归
再刷新一下浏览器,我们会发现并没有任何的内容,这是为什么呢?看到index.js应该知道,我们给原来的 a single react 多套了一层span,所以我们应该把ReactDOM.render再改动下, 改为递归的方式执行,就可以解决掉这个问题:
// 创建一个ReactDOM对象,后面导出
const ReactDOM = {};
/*
* render方法
* 2个参数:vnode 就是createElement返回的对象;container就是dom节点,如:document.getELementById('app')
*
*/
ReactDOM.render = function (vnode, container) {
// 做一下容错判断,如果没有传入container则退出
if (!container) {
return;
}
let element;
// 判断传入的vnode是不是string,如果是直接创建文本节点
if (typeof vnode === 'string') {
element = document.createTextNode(vnode);
} else {
// 如果是vnode对象,则直接创建html节点
element = document.createElement(vnode.type);
}
// 将节点元素插入html中
container.appendChild(element);
/*
* 判断是否有children
* 有的话,遍历children并递归执行下ReactDOM.render
*
*/
if (vnode.children && vnode.children.length) {
vnode.children.forEach((child) => {
/*
* 传入每个child
* 同时传入当前element作为dom节点
*
*/
ReactDOM.render(child, element);
});
}
};
export default ReactDOM;
总结
此时,我们再刷新一下浏览器,就会发现运行正常显示了 a single react ,一个简单版的React第一步就算实现了。下一篇,我会带大家来实现React属性功能,有错误之处欢迎在评论中指出。