配置环境
- 新建react_simple目录,并新建index.html和index.js两个文件。
- 全局安装parcel,一个比webpack更简单的打包工具。
npm install parcel -g
然后在开发环境中安装parcel-bundler
npm install parcel-bundler --save-dev
- 安装babel,将jsx转换为js对象(虚拟DOM)
npm i babel-core babel-preset-env babel-plugin-transform-react-jsx --save-dev
- 新建.babelrc,配置如下信息
{
"presets": ["env"],
"plugins": [
["transform-react-jsx",{
"prama":"React.createElement"
}]
]
}
- 在package.json里面加入scripts,用来启动parcel
{
"scripts": {
"start":"parcel index.html"
},
"dependencies": {
"babel-core": "^6.26.3",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.7.0",
"parcel-bundler": "^1.12.4"
}
}
写个简单的element
在index.html中写入基本的html代码,并引入index.js文件。
<!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="box"></div>
<script src="./index.js"></script>
</body>
</html>
React和createElement的实现
在react/index.js中加入如下代码:
const React = {
createElement
}
function createElement(tag, attrs, ...childrens) {
return{
tag,
attrs,
childrens
}
}
const ele = (
<div className="active" title="123">
hello, <span>react</span>
</div>
)
console.log(ele)
命令窗口中运行:
npm start
就可以看到console输出的
{tag: "div", attrs: {…}, childrens: Array(2)}
attrs: {className: "active", title: "123"}
childrens: (2) ["hello, ", {…}]
tag: "div"
__proto__: Object
由于ele先被babel解析成:
const ele = React.createElement("div", {
className: "active",
title: "123"
}, "hello, ", React.createElement("span", null, "react"));
通过我们自己createElement,再把ele解析成xml的格式
{tag: "div", attrs: {…}, childrens: Array(2)}
attrs: {className: "active", title: "123"}
childrens: (2) ["hello, ", {…}]
tag: "div"
__proto__: Object
再创建一个react文件夹,里面放入index.js, 把根目录中index.js中的下列代码挪过来,再加上export default导出
const React = {
createElement
}
function createElement(tag, attrs, ...childrens) {
return{
tag,
attrs,
childrens
}
}
export default React
别忘记在根目录中index.js中导入这个React
import React from "./react";
ReactDOM的实现
创建react-dom文件夹,同样在里面创建index.js
在react-dom/index.js中写入如下代码
const ReactDOM = {
render
}
function render(vnode, container) {
console.log(vnode)
if(vnode === undefined) return;
//如果vnode是字符串
if(typeof vnode === 'string'){
//创建文本节点
const textNode = document.createTextNode(vnode);
return container.appendChild(textNode);
}
//否则就是个虚拟DOM对象
console.log("----v node is ---", vnode.tag, vnode.attrs)
const {tag, attrs} = vnode; //解构vnode
//创建节点对象
const dom = document.createElement(tag);
if(attrs){
//有属性
Object.keys(attrs).forEach(key=>{
const value = attrs[key];
setAttribute(dom, key, value)
})
}
//递归--渲染子节点
vnode.childrens.forEach(child=>render(child, dom))
return container.appendChild(dom);
}
function setAttribute(dom, key, value) {
//将className转为class
if(key === 'className'){
key = 'class';
}
//如果是事件,onClick,onBlur等
if(/on\w+/.test(key)){
//转小写
key.toLowerCase();
dom[key] = value || '';
}else if(key === 'style'){
if(!value || typeof value === 'string'){
dom.style.cssText = value || '';
}else if(value && typeof value === 'object'){
//for example: {width:20}
for(let k in value){
if(typeof value[k] === 'number'){
dom.style[k] = value[k] + 'px';
}else{
dom.style[k] = value[k]
}
}
}
}else if(key in dom){
dom[key] = value || '';
}if(value){
dom.setAttribute(key, value);
}else{
dom.removeAttribute(key);
}
}
export default ReactDOM
在根目录中引入
import ReactDOM from "./react-dom"
回顾一下在render函数里面做了哪些事情:
-
判断vnode类型
- vnode如果是undefined,则什么都不做
- vnode如果是string,追加到container作为节点
- vnode如果是虚拟DOM对象,把vnode结构成tag, attrs, children…
-
用createElement新建一个以tag命名节点,判断attrs中是否有值,如果有值,遍历所有的值,用setAttribute(dom, key, value)将属性添加到节点中去。
-
setAttribute(dom, key, value)中
- 先将className转为class
- 对于onClick等以on开始的事件名称,先转为小写,然后添加到dom[key]中
- 对于key为style样式:
- 如果为空,或者为string类型,则直接添加到dom.style.cssText中去
- 如果为对象, 先遍历对象:\
- 如果是数值,则加上‘px’之后赋给相应dom.style
- 如果是其他,则直接赋值给相应dom.style中
- 如果是其他属性,比如id,title等等,
- 如果dom中有key,直接赋值
- 如果有新值,用dom.setAttribute(key, value)
- 如果没有,直接移除,dom.removeAttribute
-
递归–渲染子节点 vnode.childrens.forEach(child=>render(child, dom))
两个问题:
- 为什么ReactDOM.render()必须要引入React
- 组件分为哪两种?