1、前言
作为一个刚入门的前端小白,其中如果有错误,请大神指出来,我虚心求教。
前端入门,自己可以快速上手,但是想要理解其中的原理,就需要反复的看官网,或者跟着一个老师的脚步,认真的学习(我目前就是这样的,哈哈)。跟着coderwhy老师,我真的学习到了很多的知识。
其中,也有对源码的分析,仅限于表层。
2、JSX的本质
jsx
仅仅只是React.createElement(component, props, ...children)
函数的语法糖。简单的来说,jsx 通过babel转为为React.createElement,然后在进行渲染。
示例:
<h1>james</h1>
//等价于
React.createElement('h1', null, 'james')
createElement函数
createElement(type, config, children){}
参数一: 元素名称 如 “div”
参数二:属性值,对象的形式(当然也可以为null){ classname: ‘box’, title: ‘name’}
参数三:子元素
执行完函数,最终返回一个ReactElement对象,生成虚拟DOM。
babel把jsx转为React.createElement的实例
//jsx写法
<div className="box" title=“name”>
<div className="header">
<span>我是头部</span>
</div>
<div className="content">
<span>我是主体内容</span>
</div>
<div className="footer">
<span>我是尾部</span>
</div>
</div>
=============================转化为============================
//React.createElement的大致结构
React.createElement("div",
{
className: "box",
title: "name"
},
React.createElement("div",
{
className: "header"
}, React.createElement("span", null, "\u6211\u662F\u5934\u90E8")),
React.createElement("div",
{
className: "content"
}, React.createElement("span", null, "\u6211\u662F\u4E3B\u4F53\u5185\u5BB9")),
React.createElement("div",
{
className: "footer"
}, React.createElement("span", null, "\u6211\u662F\u5C3E\u90E8"))
);
是不是跟我们想的不一样啊?createElement函数只接受三个参数,为什么最后传递五个参数呢?如果结果复杂一点,是不是参数会更加的多呢?
看下源码是怎么处理的吧。
export function createElement(type, config, children) {
let propName;
...
//把传递的参数保留在arguments,然后,去掉前面的两个参数,因为前面的参数是确切的
const childrenLength = arguments.length - 2;
if (childrenLength === 1) { //只有一个节点
props.children = children;
} else if (childrenLength > 1) { //多个节点的时候,遍历,保存在一个数组中
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray; //赋值给新的对象的children中
}
...
}
大致理了一下,处理多个参数的时候,应该还是很明白的吧。
3、虚拟DOM
什么是虚拟DOM?简单来说就是一个JS对象,程序员眼中的。
虚拟DOM的创建过程
从上面的分析我们可以知道,jsx解析成createElement函数执行,函数执行过程中,到底是怎么处理的呢?
分析源码,执行React.createElement函数,最终会被创建出来一个ReactElement对象。
export function createElement(type, config, children) {
let propName;
...
return ReactElement( //调用ReactElement(),返回一个对象
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
在上面代码执行中,createElement执行到最后的时候,又执行了ReactElement函数。那么ReactElement函数呢?
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
//建立属于该元素的属性
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
// 记录负责创建此元素的组件。
_owner: owner,
};
if (__DEV__) {
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {};
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element; //返回一个对象
};
简单的看一下,ReactElement返回的是一个对象,而这个对象就是虚拟DOM,然后通过ReactDOM.render()渲染到真实的DOM上。
ReactDOM.render(
<App />,
document.getElementById('root')
);
//就是把APP这个虚拟DOM,挂载到id为root的真实节点上
为什么要使用虚拟DOM呢?
- 很难跟踪状态发生发生的变化:原有的开发模式,我们很难跟踪到状态的改变,不方便针对我们应用程序进行调试
- 操作真实DOM性能很低:传统的开发模式会进行频繁的DOM操作,而这一的做法性能非常的低
4、react中jsx渲染的流程
jsx ---> createElment() ---> ReactElement(对象树) ---> ReactDOM.render --->真实DOM
5、React.createElment函数的源码
/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
*/
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
if (hasValidKey(config)) {
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// Children can be more than one argument, and those are transferred onto
// the newly allocated props object.
const childrenLength = arguments.length - 2; //前面的两个参数
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
这个源码,还是很好理解的。
6、结语
这是我第一次接触,看源码吧。感受呢?大部分逻辑还是看的懂(可能是跟着别人的脚步吧),在最近写代码中,要想真正的处理好bug,理解源码这个过程是不可少的。所以作为一个前端小白,还是要多学习学习。
推荐博客:
https://www.jianshu.com/p/8dc8e59844c9
这篇写的比较的详细,比我这个写的好的太多呢?如果上面的没有看懂,可以取这篇博客看一看,也许答案就在那里。