毕业生求职中,有坑联系~javascript
刚毕业不久,转行前端,自学半年多,以后去找工做,发现工做机会真的很难找,内心焦急万分。这时候前辈鼓励我,稳定心态,你所须要作的就是投资本身,当工做机会真正来临的时候,可以作到一把抓住就能够了。忽然以为这句话颇有道理,我想只要天天都有进步,哪怕一时找不到工做,我也不吃亏,顶多不能尽早赚钱。。前端
我决定找一个比较流行的框架进行深度学习,在 Vue 和 React 之间,我选择了 React,主要是由于对 React 框架比较熟,并且大厂对 React 的使用度比较高。java
这个系列的目标有三个:react
本文以 15.X 版本的 React 框架进行学习,在实现 类 React 框架以前,咱们先看看须要有什么前置知识?webpack
咱们声明以下一个组件实例。git
var component = <div>测试中</div>;
复制代码
webpack 打包的时候会自动将上面的写法转换成 React.createElement 的方式,最终返回虚拟DOM 对象,以下所示。github
var virtualDOM = {
props:{
children:['children'],
},
type:'div'
}
复制代码
实际上 React 的虚拟 DOM 对象还有一些其余属性,好比 key,ref,这里为了简单起见,本节咱们只讲解初始渲染过程,因此咱们只关注和渲染相关的
props
、type
两个关键属性,到后面讲解 diff 的时候,再去关注他们。web
上面的转换须要借助 JSX 语法解析器的能力,解析器可以将 JSX 元素编译为一个虚拟 DOM 对象,即 virtualDOM。算法
借助虚拟 DOM,咱们能够不须要真正操做一个 DOM 对象,而是将实际的 DOM 对象映射给一个 JS 对象中,经过对比虚拟 DOM 来决定是否要操做真实 DOM。
JSX 首先在编译器由 webpack 结合 babel 将 JSX 元素编译为以下格式的代码,以上面代码为例。
var component = <div>测试中</div>
复制代码
这段代码在编译期间转化为以下代码:
var component = React.createElement("div", null, "\u6D4B\u8BD5\u4E2D");
复制代码
当有两个子元素的时候,
var component = <div>测试中<button>知道了</button></div>
复制代码
编译为
var component = React.createElement("div", null, "\u6D4B\u8BD5\u4E2D", React.createElement("button", null, "\u77E5\u9053\u4E86"));
复制代码
那么,当webpack将 JSX 语法编译为 React.createElement 方法调用以后,如何再经过createElement 方法返回虚拟 DOM 对象呢?
这就要涉及到 createElement 的函数实现了,接下来咱们实现它。
首先,该方法接收一系列参数。
createElement(type, props, children1, children2, ...);
复制代码
咱们的 createElement 方法须要将这些参数组装成一个 虚拟DOM 对象输出出去,实现比较简单,以下:
function createElement(type, props, ...children){
props = {...props};
if(children.length > 0){
props.children = children;
}
return {
type,
props
}
}
复制代码
那么,有了虚拟 DOM 了,下一步要作的事就是将虚拟 DOM 转化为真实 DOM ,而且将真实 DOM 插入到页面中。
咱们来看一段经典的 React 代码。
import React from 'react';
import ReactDOM from 'react-dom';
var app = <div>首次渲染</div>;
ReactDOM.render(app, document.querySelector('#root'));
复制代码
咱们看下 render 方法如何实现。
在继续往下讲解以前,咱们先了解一下 React 中的概念。
元素 元素是指经过 createElement 方法建立出来的 JS 对象,即虚拟 DOM,它会对应 html 文档里的一个真实 DOM 片断。
组件类
组件实例。 咱们一般说组件的时候,其实有时候是指组件类,有时候是指组件实例,很容易引发歧义。接下来的篇幅,咱们会分清楚这两个概念。
看下面最简单的元素:
var component = 1;
var component1 = '2';
var component2 = true;
var component3 = null;
var component4 = undefined;
复制代码
咱们的 render 方法须要可以处理它们,处理策略以下:
针对以上这三种状况,render 的实现以下:
function render(vdom, container){
let dom = renderElement(vdom);
container.appendChild(dom);
}
function renderElement(vdom){
if(typeof vdom === 'boolean' || vdom === null || vdom === undefined){
return document.createTextNode('');
}
if(typeof vdom === 'number'){
return document.createTextNode(String(vdom));
}
if(typeof vdom === 'string'){
return document.createTextNode(vdom);
}
}
复制代码
假设这样一个元素:
var component = <div>测试中</div>;
复制代码
对应虚拟 DOM 也就是
{
props: {
children: ["测试中"]
},
type: "div"
}
复制代码
那咱们的render方法须要要怎么改进,才能够对其进行渲染呢?
很明显咱们能够根据 type 区分,对 type 为 string 类型的虚拟 DOM 单独处理,下面看下具体实现。
//改造 renderElement 方法。
function renderElement(vdom){
//基础数据类型处理
//此处略。
if(typeof vdom.type === 'string'){
return renderNativeDom(vdom);
}
}
// 将类型为原生节点的虚拟 DOM 转化为真实 DOM。
function renderNativeDom(vdom){
let dom = document.createElement(vdom.type);
const { props: { children }} = vdom;
for(var i = 0; i < children.length; i++){
// 渲染子节点
let childNode = renderElement(children[i]);
dom.appendChild(childNode);
}
return dom;
}
复制代码
再来看比较经常使用的函数组件。
function A(props){
return <div>测试中</div>
}
复制代码
上面这种方式实际上定义了一个函数组件类,注意是组件类,不是组件实例。
那么咱们用的时候,就要经过这样的方式来实例化组件了。
var component = <A />; 复制代码
咱们看下,该如何改造 renderElement 方法。
按照惯例,咱们先看下函数组件实例对应的虚拟 DOM 形式。
{
props: {},
type: ƒ B()
}
复制代码
可见,type 是一个函数,这个函数就是组件类的构造函数。因此咱们能够依然能够根据 type进行区分。
策略以下:
针对 type 为 function 的虚拟 DOM,经过执行该 function 来拿到待渲染的虚拟 DOM。
以后的策略就很简单了,递归执行 renderElement 方法就好了。
咱们看下实现
//改造 renderElement
function renderElement(vdom){
//函数组件实例的判断
if(typeof vdom.type === 'function'){
return return renderFunctionComponent(vdom);
}
}
// 增长对函数组件实例的渲染。
function renderFunctionComponent(vdom){
let inst = vdom.type(vdom.props);
return renderElement(inst);
}
复制代码
最后,咱们看下类组件,看一个最经常使用的组件类的定义。
class Header extends React.Component{
render(){
return <div>类组件实例</div>
}
}
复制代码
构造一个类组件实例:
var component = <Header />; console.log(component); 复制代码
看下这种类型实例对应的虚拟 DOM 是什么样子的。
{
props: {}
type: ƒ Header()
}
复制代码
可见,类组件实例对应的虚拟 DOM 的 type 也是 函数,因此,咱们没法单纯根据虚拟 DOM 的type 来区分函数组件和类组件的实例了,那怎么区分这两种实例呢?
还记得类组件定义的时候,要继承的 Component 抽象类吗?咱们能够根据这个特色进行区分,只须要判断当前组件实例是不是 Component 的实例便可。
看下代码实现。
// 增长组件基类 Component
class Component(){
constructor(props){
this.state = {};
this.props = props || {};
}
setState(nextState){
}
}
//改造 renderElement 方法,识别类组件和函数组件。
function renderElement(vdom){
if(typeof vdom.type === 'function'){
//区分类组件仍是函数组件。
if(vdom.type.prototype instanceof React.Component){
//类组件
return renderClassComponent(vdom);
} else {
//函数组件
return renderFunctionComponent(vdom);
}
}
}
function renderClassComponent(vdom){
let inst = new vdom.type(vdom.props);
let vd = inst.render();
return renderElement(vd);
}
复制代码
以上就是咱们 React 的第一个环节,初始渲染,代码整理以下:
function createElement(type, props, ...children){
props = {...props};
if(children.length > 0){
props.children = children;
}
return {
type,
props
}
}
class Component(){
constructor(props){
this.state = {};
this.props = props || {};
}
setState(nextState){
}
}
export default {
createElement,
Component
}
复制代码
function render(vdom, container){
let dom = renderElement(vdom);
container.appendChild(dom);
}
function renderElement(vdom){
if(typeof vdom === 'boolean' || vdom === null || vdom === undefined){
return document.createTextNode('');
}
if(typeof vdom === 'number'){
return document.createTextNode(String(vdom));
}
if(typeof vdom === 'string'){
return document.createTextNode(vdom);
}
if(typeof vdom.type === 'function'){
//区分类组件仍是函数组件。
if(vdom.type.prototype instanceof React.Component){
//类组件
return renderClassComponent(vdom);
} else {
//函数组件
return renderFunctionComponent(vdom);
}
}
}
function renderClassComponent(vdom){
let inst = new vdom.type(vdom.props);
let vd = inst.render();
return renderElement(vd);
}
function renderFunctionComponent(vdom){
let inst = vdom.type(vdom.props);
return renderElement(inst);
}
function renderNativeDom(vdom){
let dom = document.createElement(vdom.type);
const { props: { children }} = vdom;
for(var i = 0; i < children.length; i++){
// 渲染子节点
let childNode = renderElement(children[i]);
dom.appendChild(childNode);
}
return dom;
}
复制代码
本节咱们只实现了 React 的初始渲染,你们会发现咱们并无针对属性作处理,也并无针对 state 变化引起界面渲染的逻辑。
不要紧,咱们一步步来,下一节咱们讲解属性、state,以及组件生命周期的处理。
再见~