从零开始写一个React - 初始化渲染
我们知道React
组件返回的是JSX
,而JSX
将被babel
转换,在React
中是将JSX
中转换成React.createElement(type, config, children)
的形式。
class App extends Component {
render() {
return <div className="app-container">App Component</div>
}
}
// babel转换后输出的代码
var App = React.createClass({
render() {
return React.createElement("div", {
className: "app-container"
}, "App Component")
}
});
我们可以在babel
中把JSX
的pragma
转换改成自己的函数名:
{
"presets": [
"es2015"
],
"plugins": [
["transform-react-jsx", {
"pragma": "createElement" // 默认的是React.createElement, 这里我们还是用默认的
}]
]
}
下面一起看看createElement
的实现
const uselessProp = ["key", "ref", "__self", "__source"]
function createElement(type, config, ...children) {
let props = {},
key = null,
ref = null;
if (config != null) {
ref = lodash.isUndefined(config.ref) ? null : config.ref;
key = lodash.isUndefined(config.key) ? null : "" + config.key;
for (let propsName in config) {
if (uselessProp.indexOf(propsName) === -1 && hasOwnProperty(config, propsName)) {
props[propsName] = config[propsName];
}
}
// 子组件
props.children = children;
}
return new ReactElement(type, key, props, ref);
}
这里createElement
就是对参数进行处理,返回一个ReactElement(virtual-dom
),ReactElement
返回一个vnode
来表示一个节点:
class ReactElement {
constructor(type, key, props, ref) {
this.type = type;
this.key = key;
this.props = props || {};
this.ref = ref || {};
}
}
在返回的vnode
中,type
用来标识组件类型,props
就是组件中标签上的attributes
,key
用作这个vnode
的唯一标识,可用于后面的更新,现在有了vnode
,就需要将vnode
转换成真实的DOM
挂载到页面上,在React
启动的时候,是通过ReactDOM.render
来进行渲染组件到页面,渲染就是一个递归调用render
的过程:
function render(componnet, container, callback = noop) {
const componentInstance = instantiateReactComponent(componnet),
markup = componentInstance.mountComponent(React.nextReactRootIndex ++);
container.innerHTML = markup;
if (lodash.isFunction(callback)) {
callback.call(componentInstance);
}
}
在render
中我们调用了一个instantiateReactComponent
,这个函数的作用是根据vnode
里面type
的不同来返回不同的组件:
export function instantiateReactComponent(node) {
// 文本节点的情况
if (typeof node === "string" || typeof node === "number") {
return new ReactDOMTextComponent(node);
}
// 浏览器默认节点的情况
if (typeof node === "object" && typeof node.type === "string") {
return new ReactDOMComponent(node);
}
// 自定义的元素节点
if (typeof node === "object" && typeof node.type === "function") {
return new ReactCompositeComponent(node);
}
}
其中,ReactDOMTextComponent
是纯文本组件,ReactDOMComponent
是浏览器标签组件,ReactCompositeComponent
是自定义的组件,后面的两种组件类型都属于virtual-dom
。
我们接下来看看上面三个组件的实现:
// 文本组件
export class ReactDOMTextComponent {
constructor(text) {
// 存下当前的字符串
this._currentElement = ("" + text);
// 组件唯一id
this._rootNodeID = null;
}
/**
* component渲染时生成的dom结构
* @param {String} rootID [组件唯一id]
* @return {String} [HTML字符串]
*/
mountComponent(rootID) {
this._rootNodeID = rootID;
return `<!-- react-text: ${this._rootNodeID} -->${this._currentElement}<!-- /react-text -->`;
}
}
// 浏览器标签组件
export class ReactDOMComponent {
constructor(element) {
// 存下当前元素引用
this._currentElement = element;
// 组件唯一id
this._rootNodeID = null;
// 子组件集合
this._renderedChildren = null;
}
/**
* component渲染时生成的dom结构
* @param {String} rootID [组件唯一id]
* @return {String} [HTML字符串]
*/
mountComponent(rootID) {
this._rootNodeID = rootID;
const { props, type } = this._currentElement,
{ children } = props,
// 判断是否为单标签
isSingleTag = SINGLE_TAG_REG.test(type);
let tagOpen,
tagClose,
propKey,
propValue,
eventType,
childrenInstances,
childComponentInstance,
childrenMarkups,
curRootId;
tagOpen = [];
tagOpen.push(`<${type}`, `data-reactid="${this._rootNodeID}"`);
for (propKey in props) {
// 过滤掉propKey为"children"的情况
if (hasOwnProperty(props, propKey) && propKey !== "children") {
propValue = props[propKey];
if (/^on[a-z]+/i.test(propKey)) {
/**
* handleClick() {
* alert("clicked");
* }
*
* render() {
* return (
* <div onClick={this.handleClick}>click me</div>
* );
* }
*/
// 取得事件名称并进行事件代理
eventType = propKey.replace("on", "").toLowerCase();
Event.delegate({
element: doc,
type: eventType,
selector: `[data-reactid="${this._rootNodeID}"]`,
handler: propValue,
context: null
});
// TODO: 实现对当前元素进行事件代理
} else if (propKey === "style") {
/**
* render() {
* return (
* <div style={"background: red;"}></div>
* );
* }
*
* -----------------------
*
* render() {
* return (
* <div style={{background: 'red'}}>
* </div>
* );
* }
*/
if (lodash.isObject(propValue)) {
// 这里的toStyle用了一个npm包(https://www.npmjs.com/package/to-style)
propValue = toStyle.string(propValue);
}
tagOpen.push(`style="${propValue}"`);
} else if (propValue === "className") {
tagOpen.push(`class="${propValue}"`);
} else {
tagOpen.push(`${propKey}="${propValue}"`);
}
}
}
if (isSingleTag) {
tagOpen.push("/>");
tagClose = "";
} else {
tagOpen.push(">");
tagClose = `</${type}>`;
}
childrenMarkups = [];
childrenInstances = [];
// 遍历子节点,实例化->渲染子节点
if (children && children.length) {
children.forEach((child, key) => {
childComponentInstance = instantiateReactComponent(child);
childrenInstances.push(childComponentInstance);
childComponentInstance._mountIndex = key;
// 拼一个类似于"x.y"的子节点的nodeId
curRootId = `${this._rootNodeID}.${key}`;
childrenMarkups.push(childComponentInstance.mountComponent.call(childComponentInstance, curRootId));
});
}
// 将子组件的集合保留起来,留作后面更新使用
this._renderedChildren = childrenInstances;
// 返回组件的真实HTML结构
return `${tagOpen.join(" ")} ${childrenMarkups.join(" ")} ${tagClose}`;
}
}
// React自定义组件
export class ReactCompositeComponent {
constructor(element) {
// 存放元素element对象
this._currentElement = element;
// 存放唯一标识
this._rootNodeID = null;
// 存放对应的ReactClass的实例
this._instance = null;
}
mountComponent(rootID, hostContainerInfo, context) {
this._rootNodeID = rootID;
const { props, type } = this._currentElement,
ReactClass = type,
// 根据vnode中的type来实例化
// 在instantiateReactComponent中可以看到,只有当vnode的type是一个方法类型(组件的constructor)时才会返回ReactCompositeComponent
inst = new ReactClass(props);
let renderedElement, renderedComponentInstance, renderedMarkup;
// 保持对当前组件实例的引用
this._instance = inst;
inst._reactInternalInstance = this;
// 执行生命周期中的componentWillMount
if (lodash.isFunction(inst.componentWillMount)) {
inst.componentWillMount();
}
// 调用当前组件的render
renderedElement = this._instance.render();
// 根据render中返回的再去执行instantiateReactComponent
renderedComponentInstance = instantiateReactComponent(renderedElement);
this._renderedComponent = renderedComponentInstance;
// 挂载组件
renderedMarkup = renderedComponentInstance.mountComponent(this._rootNodeID);
if (lodash.isFunction(inst.componentDidMount)) {
inst.componentDidMount();
}
return renderedMarkup;
}
receiveComponent(nextElement, newState) {}
}
上面就是我们对三种组件的一个实现,下面我们用一个图简单表示下具体的渲染过程。
接下来我们需要写一个Component
基类,去给所有的子类继承:
export class Component {
constructor() {}
setState(newState) {
this._reactInternalInstance.receiveComponent(null, newState);
}
/**
* 组件即将被挂载到DOM上
*/
componentWillMount() {}
/**
* 组件已经被挂载到DOM上
*/
componentDidMount() {}
/**
* 组件即将收到新的props
* @param {Object} nextProps [新的props]
* @param {Object} nextState [新的state]
*/
componentWillReceiveProps(nextProps, nextState) {}
/**
* 组件是否应该更新
* @param {Object} nextProps [新的props]
* @param {Object} nextState [新的state]
* @return {Boolean} [标记组件是否应该更新]
*/
shouldComponentUpdate(nextProps, nextState) {
return lodash.isEqual(this.state, nextState) || lodash.isEqual(this.props, nextProps);
}
/**
* 组件即将更新
*/
componentWillUpdate() {}
/**
* 组件已经更新
*/
componentDidUpdate() {}
/**
* 组件即将被卸载
*/
componentWillUnmount() {}
/**
* 返回组件内部的jsx
* @return {JSX} [组件布局]
*/
render() {}
}
下面我们一起根据上面的代码来完成一个初始化渲染的demo:
import React, { Component } from "./src/react";
class InputComponnet extends Component {
constructor() {
super();
}
keyUpHandler(ev) {
console.log(this);
}
render() {
return (
<input
type = "text"
ref="textInput"
placeholder = { "请输入..." }
onKeyUp = { this.keyUpHandler.bind(this) }
style = {{
display: "block",
width: "200px",
height: "30px",
fontSize: "14px",
lineHeight: "30px"
}} />
);
}
}
class App extends Component {
constructor() {
super();
}
render() {
return (
<div style = { "background: red;" } >
App Component <InputComponnet placeholder = { "请输入..." }/>
</div>
);
}
}
React.render( < App / > , document.querySelector("#root"));
到这里我们的初始化渲染已经完成了,我们实现了组件中的事件绑定(事件代理),简单的生命周期(componentWillMout
, componentDidMount
),将virtual-dom
转换成真实的DOM
并且挂载到页面的过程。
这里可以查看相对完整的代码。