基于babel实现react核心功能(初始化,fiber,hook)


为什么我会基于babel来实现react,因为jsx浏览器是无法识别的,所以我通过babel编译jsx为js,在手撕源码实现就ok了,废话不多说上才艺,我哩giao。

前方高能,请做好准备

项目结构


首先把相关的插件都装好
package.json

{
  "name": "source",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --sourcemap --inline --progress --config build/webpack.dev.config.js",
    "build": "cross-env NODE_ENV=production webpack --progress --config build/webpack.prod.config.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {},
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/plugin-proposal-class-properties": "^7.10.4",
    "@babel/preset-env": "^7.11.5",
    "@babel/preset-react": "^7.10.4",
    "babel-loader": "^8.1.0",
    "cross-env": "^7.0.2",
    "css-loader": "^4.3.0",
    "html-webpack-plugin": "^4.4.1",
    "less-loader": "^7.0.1",
    "style-loader": "^1.2.1",
    "webpack": "^4.44.1",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ]
}

安装html-webpack-plugin是为了让webpack找到html文件并输出到浏览器,babel-loader加上babel插件将jsx和一些语法进行转换和polyfill

// webpack.dev.config.js
const {resolve, posix:{join}} = require('path');
const HTMLPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: {
    app: "./src/index"
  },
  // 出口
  output: {
    path : resolve(__dirname,"../dist"),
    filename: join("static", "js/[name].[hash].js") ,
    chunkFilename: join("static", "js/[name].[chunkhash].js"),
    publicPath: "/" // 打包后的资源的访问路径前缀
  },
  module: { // 所有第三方模块的匹配规则, webpack默认只能处理.js后缀名的文件,需要安装第三方loader
    rules: [
      {
        test: /\.m?js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
            plugins: ["@babel/plugin-proposal-class-properties"]
          }
        },
        exclude: /(node_modules|bower_components)/, // 千万别忘记添加exclude选项,不然运行可能会报错
      },
      {
        test: /\.less$/,
        use: [
          // {
          //   loader:MiniCssExtractPlugin.loader,
          //   options:{
          //     hmr: utils.isDev(),  // 开发环境热更新 ,然而不起作用
          //     reloadAll:true,
          //   }
          // },
          {
              loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'postcss-loader'
          },
          {
            loader: 'less-loader', // 编译 Less -> CSS
          },
        ],
      }
    ]
  },
  plugins: [
    new HTMLPlugin(
      {
        filename: resolve(__dirname, './../dist/index.html'), // html模板的生成路径
        template: './public/index.html',//html模板
        inject: true, // true:默认值,script标签位于html文件的 body 底部
      }
    )
  ],
  resolve: {
    extensions: ['.js', 'json'],
    alias: {
      '@': join(__dirname, '..', 'src')
    }
  },
  devtool: "#eval-source-map",
  devServer: {
    historyApiFallback: true, // 当找不到路径的时候,默认加载index.html文件
    hot: true,
    contentBase: false, // 告诉服务器从哪里提供内容。只有在你想要提供静态文件时才需要
    compress: true, // 一切服务都启用gzip 压缩:
    port: "3005", // 指定段靠谱
    publicPath: "/", // 访问资源加前缀
  }
}

.babelrc

{ "presets":["@babel/react","@babel/env"]}

webpack的入口文件
./src/index.js

import Component from './react/component';
import React from './react';
import ReactDom from './react/react-dom';
class ClassComponent extends Component {
  render () {
    return (<div><button>666</button></div>)
  }
}
const jsx = (<div>
  <ClassComponent />
</div>)
ReactDom.render(jsx, document.getElementById('root'))

接下来是实现react源码的代码,我也不爱废话,上代码

jsx会根据每一个节点,转译成React.createElement,比如上面我写的标签其实最后会变成下面的样子

// 下面的null的位置是属性,我上面一个属性都没有所以是null
React.createElement('div', null, React.createElement(ClassComponent, null))

看到这里你会想知道这个React.createElement是怎么实现的呀,感觉有点意思了,那就继续看吧
./src/react/const.js

export const TEXT = 'TEXT';
export const PLACEMENT = "PLACEMENT";
export const UPDATE = "UPDATE";
export const DELETION = "DELETION";

./src/react/component.js
export default class {
static isReactComponent = {}
constructor (props) {
this.props = props;
}
}

./src/react/index.js
``` javascript
import {TEXT} from './const';
function createElement (type, config, ...children) {
  const props = {
    ...(config || {}),
    // 这里判断是否为文本,不是文本就是虚拟节点
    children: children.map(child => typeof child === 'object' ? child:{
      type: TEXT,
      props: {
        children: [],
        nodeValue: child
      }
    })
  }
  return {type, props}
}

export default {createElement}

上面的代码执行完应该就会生成虚拟dom树了
接下来我们来实现fiber架构的diff,把虚拟节点变真实节点,也就是把js所描述的对象变成真正的dom插入到页面中
虚拟dom转换成真实dom靠的就是一手新旧节点的比较,而react把它优化的非常nice,通过调用requestIdleCallback让页面更加丝滑,,具体我不想讲了,不懂看看别的博客
./src/react/react-dom.js

import {TEXT, PLACEMENT, UPDATE, DELETION} from "./const";
// 下一个单元任务
let nextUnitOfWork = null;
// work in progress fiber root
let wipRoot = null;
// 现在的根节点
let currentRoot = null;
let deletions = null;

function render(createVnode, container) {
  let vnode;
  if (createVnode.isReactComponent) {
    vnode = new createVnode().render();
  } else if (typeof createVnode === 'function') {
    vnode = createVnode();
  } else {
    vnode = createVnode;
  }
  wipRoot = {
    node: container,
    props: {
      children: [vnode]
    },
    base: currentRoot
  };
  nextUnitOfWork = wipRoot;
  deletions = [];
}
// vnode->node
// 生成成node节点
function createNode(vnode) {
  const {type, props} = vnode;
  let node = null;
  if (type === TEXT) {
    node = document.createTextNode("");
  } else if (typeof type === "string") {
    node = document.createElement(type);
  }
  updateNode(node, {}, props);
  return node;
}
function reconcileChildren(workInProgressFiber, children) {
// 构建fiber结构
// 更新 删除 新增
  let prevSibling = null;
  let oldFiber = workInProgressFiber.base && workInProgressFiber.base.child;
  for (let i = 0; i < children.length; i++) {
    let child = children[i];
    let newFiber = null;
    const sameType = child && oldFiber && child.type === oldFiber.type;
    if (sameType) {
// 类型相同 复用
      newFiber = {
        type: oldFiber.type,
        props: child.props,
        node: oldFiber.node,
        base: oldFiber,
        return: workInProgressFiber,
        effectTag: UPDATE
      };
    }
    if (!sameType && child) {
// 类型不同 child存在 新增插入
      newFiber = {
        type: child.type,
        props: child.props,
        node: null,
        base: null,
        return: workInProgressFiber,
        effectTag: PLACEMENT
      };
    }
    if (!sameType && oldFiber) {
// 删除
      oldFiber.effectTag = DELETION;
      deletions.push(oldFiber);
    }
    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }
// 形成链表结构
    if (i === 0) {
      workInProgressFiber.child = newFiber;
    } else {
// i>0
      prevSibling.sibling = newFiber;
    }
    prevSibling = newFiber;
  }
}
function updateNode(node, preVal, nextVal) {
  Object.keys(preVal)
    .filter(k => k !== "children")
    .forEach(k => {
      if (k.slice(0, 2) === "on") {
// 简单处理 on开头当做事件
        let eventName = k.slice(2).toLowerCase();
        node.removeEventListener(eventName, preVal[k]);
      } else {
        if (!(k in nextVal)) {
          node[k] = "";
        }
      }
    });
  Object.keys(nextVal)
    .filter(k => k !== "children")
    .forEach(k => {
      if (k.slice(0, 2) === "on") {
// 简单处理 on开头当做事件
        let eventName = k.slice(2).toLowerCase();
        node.addEventListener(eventName, nextVal[k]);
      } else {
        node[k] = nextVal[k];
      }
    });
}
function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  wipFiber.hooks = [];
  hookIndex = 0;
  const {type, props} = fiber;
  const children = [type(props)];
  reconcileChildren(fiber, children);
}
function updateClassComponent(fiber) {
  wipFiber = fiber;
  wipFiber.hooks = [];
  hookIndex = 0;
  const {type, props} = fiber;
  const children = [new type(props).render()];
  reconcileChildren(fiber, children);
}
function performUnitOfWork(fiber) {
// 1. 执行当前任务
// 执行当前任务
  const {type} = fiber;
  if (typeof type === "function") {
    type.isReactComponent
      ? updateClassComponent(fiber)
      : updateFunctionComponent(fiber);
  } else {
// 原生标签
    updateHostComponent(fiber);
  }
// 2、 返回下一个任务
// 原则就是:先找子元素
  if (fiber.child) {
    return fiber.child;
  }
// 如果没有子元素 寻找兄弟元素
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.return;
  }
}
function updateHostComponent(fiber) {
  if (!fiber.node) {
    fiber.node = createNode(fiber);
  }
// todo reconcileChildren
  const {children} = fiber.props;
  reconcileChildren(fiber, children);
}
function workLoop(deadline) {
// 有下一个任务,并且当前帧还没有结束
  while (nextUnitOfWork && deadline.timeRemaining() > 1) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  if (!nextUnitOfWork && wipRoot) {
    commitRoot();
  }
  requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
// ! commit阶段
function commitRoot() {
  deletions.forEach(commitWorker);
  commitWorker(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWorker(fiber) {
  if (!fiber) {
    return;
  }
// 向上查找
  let parentNodeFiber = fiber.return;
  while (!parentNodeFiber.node) {
    parentNodeFiber = parentNodeFiber.return;
  }
  const parentNode = parentNodeFiber.node;
  if (fiber.effectTag === PLACEMENT && fiber.node !== null) {
    parentNode.appendChild(fiber.node);
  } else if (fiber.effectTag === UPDATE && fiber.node !== null) {
    updateNode(fiber.node, fiber.base.props, fiber.props);
  } else if (fiber.effectTag === DELETION && fiber.node !== null) {
    commitDeletions(fiber, parentNode);
  }
  commitWorker(fiber.child);
  commitWorker(fiber.sibling);
}
function commitDeletions(fiber, parentNode) {
  if (fiber.node) {
    parentNode.removeChild(fiber.node);
  } else {
    commitDeletions(fiber.child, parentNode);
  }
}
// !hook 实现
// 当前正在工作的fiber
let wipFiber = null;
let hookIndex = null;
export function useState(init) {
  const oldHook = wipFiber.base && wipFiber.base.hooks[hookIndex];
  const hook = {state: oldHook ? oldHook.state : init, queue: []};
  const actions = oldHook ? oldHook.queue : [];
  actions.forEach(action => (hook.state = action));
  const setState = action => {
    hook.queue.push(action);
    wipRoot = {
      node: currentRoot.node,
      props: currentRoot.props,
      base: currentRoot
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };
  wipFiber.hooks.push(hook);
  hookIndex++;
  return [hook.state, setState];
}


export default {render};

posted @ 2020-09-12 16:40  代码男孩  阅读(371)  评论(0编辑  收藏  举报