zoukankan      html  css  js  c++  java
  • 基于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};
    
    
  • 相关阅读:
    【LeetCode】048. Rotate Image
    【LeetCode】036. Valid Sudoku
    【LeetCode】060. Permutation Sequence
    【LeetCode】001. Two Sum
    【LeetCode】128. Longest Consecutive Sequence
    【LeetCode】081. Search in Rotated Sorted Array II
    【LeetCode】033. Search in Rotated Sorted Array
    顺时针打印矩阵
    矩形覆盖
    二维数组中的查找
  • 原文地址:https://www.cnblogs.com/smallZoro/p/13657375.html
Copyright © 2011-2022 走看看