• 基于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};
    
    
  • 相关阅读:
    技术学习沙龙
    mysql升级5.5
    mysql用户权限管理的问题
    dwz(jui)刷新当前dialog的方法
    perl进程管理一例
    cron执行service
    tp数据库表大写命名的一些问题
    php执行多个存储过程
    thinkphp使用中遇到的问题
    html5 ajax 文件上传
  • 原文地址:https://www.cnblogs.com/smallZoro/p/13657375.html
Copyright © 2020-2023  润新知