教你如何从 html 实现一个 react

什么是 React

React是一个简单的javascript UI库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从高阶组件到低阶组件的单向数据流。

前言 📝

👉 我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。官网地址

react 的理念是在于对大型项目的快速响应,对于新版的 react 16.8 而言更是带来的全新的理念fiber去解决网页快速响应时所伴随的问题,即 CPU 的瓶颈,传统网页浏览受制于浏览器刷新率、js 执行时间过长等因素会造成页面掉帧,甚至卡顿

react 由于自身的底层设计从而规避这一问题的发生,所以 react16.8 的面世对于前端领域只办三件事:快速响应、快速响应、还是 Tmd 快速响应 !,这篇文章将会从一个 html 出发,跟随 react 的 fiber 理念,仿一个非常基础的 react

一开始的准备工作 🤖

html

我们需要一个 html 去撑起来整个页面,支撑 react 运行,页面中添加<div></div>,之后添加一个 script 标签,因为需要使用import进行模块化构建,所以需要为 script 添加 type 为module的属性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="root"></div>
  <script type="module" src="./index.js" ></script>
</body>

</html>

推荐安装一个 Live Server 插件,有助于我们对代码进行调试,接下来的操作也会用到

JavaScript

我们会仿写一个如下的 react,实现一个基础的操作,在 <input/> 绑定事件,将输入的值插入在 <h2/> 标签内:

...
function App() {
  return (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
      <hr />
    </div>
  );
}
...

在 react 进行 babel 编译的时候,会将 JSX 语法转化为 React.createElement() 的形式,如上被 retuen 的代码就会被转换成

...
React.createElement(
  "div",
  null,
  React.createElement("input", {
    onInput: updateValue,
    value: value,
  }),
  React.createElement("h2", null, "Hello ", value),
  React.createElement("hr", null)
);
...

在线地址

从转换后的代码我们可以看出 React.createElement 支持多个参数:

  • type,节点类型
  • config, 节点上的属性,比如 id 和 href
  • children, 子元素了,子元素可以有多个,类型可以是简单的文本,也可以还是 React.createElement,如果是 React.createElement,其实就是子节点了,子节点下面还可以有子节点。这样就用 React.createElement 的嵌套关系实现了 HTML 节点的树形结构。

我们可以按照 React.createElement 的形式仿写一个可以实现同样功能的 createElement 将 jsx 通过一种简单的数据结构展示出来即 虚拟DOM 这样在更新时,新旧节点的对比也可以转化为虚拟 DOM 的对比

{
  type:'节点标签',
  props:{
    props:'节点上的属性,包括事件、类...',
    children:'节点的子节点'
  }
}

这里我们可以写一个函数实现下列需求

  • 原则是将所有的参数返回到一个对象上
  • children 也要放到 props 里面去,这样我们在组件里面就能通过 props.children 拿到子元素
  • 当子组件是文本节点时,通过构造一种 type 为 TEXT_ELEMENT 的节点类型表示
/**
 * 创建虚拟 DOM 结构
 * @param {type} 标签名
 * @param {props} 属性对象
 * @param {children} 子节点
 * @return {element} 虚拟 DOM
 */
const createElement = (type, props, ...children) => ({
  type,
  props: {
    ...props,
    children: children.map(child =>
      typeof child === "object"
        ? child
        : {
            type: "TEXT_ELEMENT",
            props: {
              nodeValue: child,
              children: [],
            },
          }
    ),
  },
});

react 中 createElement 源码实现

实现 createElement 之后我们可以拿到虚拟 DOM,但是还需要 render 将代码渲染到页面,此时我们需要对 index.js 进行处理,添加输入事件,将 createElementrender 通过 import 进行引入,render 时传入被编译后的虚拟 DOM 和页面的根元素 root, 最后再进行executeRender调用,页面被渲染,在页面更新的时候再次调用executeRender进行更新渲染

import {createElement,render} from "./mini/index.js";
const updateValue = e => executeRender(e.target.value);
const executeRender = (value = "World") => {
  const element = createElement(
    "div",
    null,
    createElement("input", {
      onInput: updateValue,
      value: value,
    }),
    createElement("h2", null, "Hello ", value),
    createElement("hr", null)
  );
  render(element, document.getElementById("root"));
};

executeRender();

render 的时候做了什么 🥔

before 版本

render 函数帮助我们将 element 添加至真实节点中,首先它接受两个参数:

根组件,其实是一个 JSX 组件,也就是一个 createElement 返回的虚拟 DOM

父节点,也就是我们要将这个虚拟 DOM 渲染的位置

在 react 16.8 之前,渲染的方法是通过一下几步进行的

  • 创建 element.type 类型的 dom 节点,并添加到 root 元素下(文本节点特殊处理)
  • 将 element 的 props 添加到对应的 DOM 上,事件进行特殊处理,挂载到 document 上(react17 调整为挂在到 container 上)
  • 将 element.children 循环添加至 dom 节点中;

拿到虚拟 dom 进行如上三步的递归调用,渲染出页面 类似于如下流程

/**
 * 将虚拟 DOM 添加至真实 DOM
 * @param {element} 虚拟 DOM
 * @param {container} 真实 DOM
 */
const render = (element, container) => {
  let dom;
  /*
      处理节点(包括文本节点)
  */
  if (typeof element !== "object") {
    dom = document.createTextNode(element);
  } else {
    dom = document.createElement(element.type);
  }
  /*
      处理属性(包括事件属性)
  */
  if (element.props) {
    Object.keys(element.props)
      .filter((key) => key != "children")
      .forEach((item) => {
        dom[item] = element.props[item];
      });
    Object.keys(element.props)
      .filter((key) => key.startsWith("on"))
      .forEach((name) => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, nextProps[name]);
      });
  }
  if (
    element.props &&
    element.props.children &&
    element.props.children.length
  ) {
    /*
      循环添加到dom
  */
    element.props.children.forEach((child) => render(child, dom));
  }
  container.appendChild(dom);
};

after 版本(fiber)

当我们写完如上的代码,会发现这个递归调用是有问题的

如上这部分工作被 React 官方称为 renderer,renderer 是第三方可以自己实现的一个模块,还有个核心模块叫做 reconsiler,reconsiler 的一大功能就是 diff 算法,他会计算出应该更新哪些页面节点,然后将需要更新的节点虚拟 DOM 传递给 renderer,renderer 负责将这些节点渲染到页面上,但是但是他却是同步的,一旦开始渲染,就会将所有节点及其子节点全部渲染完成这个进程才会结束。

React 的官方演讲中有个例子,可以很明显的看到这种同步计算造成的卡顿:

当 dom tree 很大的情况下,JS 线程的运行时间可能会比较长,在这段时间浏览器是不会响应其他事件的,因为 JS 线程和 GUI 线程是互斥的,JS 运行时页面就不会响应,这个时间太长了,用户就可能看到卡顿,

此时我们可以分为两步解决这个问题

  • 允许中断渲染工作,如果有优先级更高的工作插入,则暂时中断浏览器渲染,待完成该工作后,恢复浏览器渲染;
  • 将渲染工作进行分解,分解成一个个小单元;

solution I 引入一个新的 Api

requestIdleCallback 接收一个回调,这个回调会在浏览器空闲时调用,每次调用会传入一个 IdleDeadline,可以拿到当前还空余多久, options 可以传入参数最多等多久,等到了时间浏览器还不空就强制执行了。

window.requestIdleCallback 将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件

但是这个 API 还在实验中,兼容性不好,所以 React 官方自己实现了一套。本文会继续使用 requestIdleCallback 来进行任务调度

// 下一个工作单元
let nextUnitOfWork = null
/**
 * workLoop 工作循环函数
 * @param {deadline} 截止时间
 */
function workLoop(deadline) {
  // 是否应该停止工作循环函数
  let shouldYield = false

  // 如果存在下一个工作单元,且没有优先级更高的其他工作时,循环执行
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )

    // 如果截止时间快到了,停止工作循环函数
    shouldYield = deadline.timeRemaining() < 1
  }

  // 通知浏览器,空闲时间应该执行 workLoop
  requestIdleCallback(workLoop)
}
// 通知浏览器,空闲时间应该执行 workLoop
requestIdleCallback(workLoop)

// 执行单元事件,并返回下一个单元事件
function performUnitOfWork(nextUnitOfWork) {
  // TODO
}

solution II 创建 fiber 的数据结构

Fiber 之前的数据结构是一棵树,父节点的 children 指向了子节点,但是只有这一个指针是不能实现中断继续的。比如我现在有一个父节点 A,A 有三个子节点 B,C,D,当我遍历到 C 的时候中断了,重新开始的时候,其实我是不知道 C 下面该执行哪个的,因为只知道 C,并没有指针指向他的父节点,也没有指针指向他的兄弟。

Fiber 就是改造了这样一个结构,加上了指向父节点和兄弟节点的指针:

  • child 指向子组件
  • sibling 指向兄弟组件
  • return 指向父组件

每个 fiber 都有一个链接指向它的第一个子节点、下一个兄弟节点和它的父节点。这种数据结构可以让我们更方便的查找下一个工作单元,假定 A 是挂在 root 上的节点 fiber 的渲染顺序也如下步骤

  • 从 root 开始,找到第一个子节点 A;
  • 找到 A 的第一个子节点 B
  • 找到 B 的第一个子节点 E
  • 找 E 的第一个子节点,如无子节点,则找下一个兄弟节点,找到 E 的兄弟节点 F
  • 找 F 的第一个子节点,如无子节点,也无兄弟节点,则找它的父节点的下一个兄弟节点,找到 F 的 父节点的兄弟节点 C;
  • 找 C 的第一个子节点,找不到,找兄弟节点,D
  • 找 D 的第一个子节点,G
  • 找 G 的第一个子节点,找不到,找兄弟节点,找不到,找父节点 D 的兄弟节点,也找不到,继续找 D 的父节点的兄弟节点,找到 root;
  • 上一步已经找到了 root 节点,渲染已全部完成。

我们通过这个数据结构实现一个 fiber

//创建最初的根fiber
 wipRoot = {
  dom: container,
  props: { children: [element] },
};
performUnitOfWork(wipRoot);

随后调用performUnitOfWork自上而下构造整个 fiber 树

/**
 * performUnitOfWork用来执行任务
 * @param {fiber} 我们的当前fiber任务
 * @return {fiber} 下一个任务fiber任务
 */
const  performUnitOfWork = fiber => {
  if (!fiber.dom) fiber.dom = createDom(fiber); // 创建一个DOM挂载上去
  const elements = fiber.props.children; //当前元素下的所有同级节点
  // 如果有父节点,将当前节点挂载到父节点上
  if (fiber.return) {
    fiber.return.dom.appendChild(fiber.dom);
  }

  let prevSibling = null;
  /*
      之后代码中我们将把此处的逻辑进行抽离
  */
  if (elements && elements.length) {
    elements.forEach((element, index) => {
      const newFiber = {
        type: element.type,
        props: element.props,
        return: fiber,
        dom: null,
      };
      // 父级的child指向第一个子元素
      if (index === 0) {
        fiber.child = newFiber;
      } else {
        // 每个子元素拥有指向下一个子元素的指针
        prevSibling.sibling = newFiber;
      }
      prevSibling = fiber;
    });
  }
  // 先找子元素,没有子元素了就找兄弟元素
  // 兄弟元素也没有了就返回父元素
  // 最后到根节点结束
  // 这个遍历的顺序是从上到下,从左到右
  if (fiber.child) {
    return fiber.child;
  } else {
    let nextFiber = fiber;
    while (nextFiber) {
      if (nextFiber.sibling) {
        return nextFiber.sibling;
      }
      nextFiber = nextFiber.return;
    }
  }
}

after 版本(reconcile)

currentRoot

reconcile 其实就是虚拟 DOM 树的 diff 操作,将更新前的 fiber tree 和更新后的 fiber tree 进行比较,得到比较结果后,仅对有变化的 fiber 对应的 dom 节点进行更新。

  • 删除不需要的节点
  • 更新修改过的节点
  • 添加新的节点

新增 currentRoot 变量,保存根节点更新前的 fiber tree,为 fiber 新增 alternate 属性,保存 fiber 更新前的 fiber tree

let currentRoot = null
function render (element, container) {
    wipRoot = {
        // 省略
        alternate: currentRoot
    }
}
function commitRoot () {
    commitWork(wipRoot.child)
    /*
        更改fiber树的指向,将缓存中的fiber树替换到页面中的fiber tree
    */
    currentRoot = wipRoot
    wipRoot = null
}
  • 如果新老节点类型一样,复用老节点 DOM,更新 props
  • 如果类型不一样,而且新的节点存在,创建新节点替换老节点
  • 如果类型不一样,没有新节点,有老节点,删除老节点

reconcileChildren

  • 将 performUnitOfWork 中关于新建 fiber 的逻辑,抽离到 reconcileChildren 函数
  • 在 reconcileChildren 中对比新旧 fiber;

在对比 fiber tree 时

  • 当新旧 fiber 类型相同时 保留 dom,仅更新 props,设置 effectTag 为 UPDATE
  • 当新旧 fiber 类型不同,且有新元素时 创建一个新的 dom 节点,设置 effectTag 为 PLACEMENT
  • 当新旧 fiber 类型不同,且有旧 fiber 时 删除旧 fiber,设置 effectTag 为 DELETION
/**
 * 协调子节点
 * @param {fiber} fiber
 * @param {elements} fiber 的 子节点
 */
function reconcileChildren(wipFiber, elements) {
  let index = 0;// 用于统计子节点的索引值
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child; //更新时才会产生
  let prevSibling;// 上一个兄弟节点
  while (index < elements.length || oldFiber) {
    /**
     * 遍历子节点
     * oldFiber判断是更新触发还是首次触发,更新触发时为元素下所有节点
     */
    let newFiber;
    const element = elements[index];
    const sameType = oldFiber && element && element.type == oldFiber.type; // fiber 类型是否相同点
    /**
     * 更新时
     * 同标签不同属性,更新属性
     */
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props, //只更新属性
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE",
      };
    }
    /**
     * 不同标签,即替换了标签 or 创建新标签
     */
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT",
      };
    }
    /**
     * 节点被删除了
     */
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION";
      deletions.push(oldFiber);
    }

    if (oldFiber) oldFiber = oldFiber.sibling;
    // 父级的child指向第一个子元素
    if (index === 0) {
      // fiber的第一个子节点是它的子节点
      wipFiber.child = newFiber;
    } else {
      // fiber 的其他子节点,是它第一个子节点的兄弟节点
      prevSibling.sibling = newFiber;
    }
    // 把新建的 newFiber 赋值给 prevSibling,这样就方便为 newFiber 添加兄弟节点了
    prevSibling = newFiber;
    //  索引值 + 1
    index++;
  }
}

在 commit 时,根据 fiber 节点上effectTag的属性执行不同的渲染操作

after 版本(commit)

在 commitWork 中对 fiber 的 effectTag 进行判断,处理真正的 DOM 操作。

  • 当 fiber 的 effectTag 为 PLACEMENT 时,表示是新增 fiber,将该节点新增至父节点中。
  • 当 fiber 的 effectTag 为 DELETION 时,表示是删除 fiber,将父节点的该节点删除。
  • 当 fiber 的 effectTag 为 UPDATE 时,表示是更新 fiber,更新 props 属性。
/**
 * @param {fiber} fiber 结构的虚拟dom
 */
function commitWork(fiber) {
  if (!fiber) return;
  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom);
  }

  // 递归操作子元素和兄弟元素
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

此时我们着重来看updateDom发生了什么,我们拿到 dom 上被改变的新旧属性,进行操作

/*
    isEvent :拿到事件属性
    isProperty :拿到非节点、非事件属性
    isNew :拿到前后改变的属性
*/
const isEvent = key => key.startsWith("on");
const isProperty = key => key !== "children" && !isEvent(key);
const isNew = (prev, next) => key => prev[key] !== next[key];

/**
 * 更新dom属性
 * @param {dom} fiber dom
 * @param {prevProps} fiber dom上旧的属性
 * @param {nextProps} fiber dom上新的属性
 */
function updateDom(dom, prevProps, nextProps) {
  /**
   * 便利旧属性
   * 1、拿到on开头的事件属性
   * 2、拿到被删除的事件
   * 3、已删除的事件取消监听
   */
  Object.keys(prevProps)
    .filter(isEvent)
    .filter(key => !(key in nextProps))
    .forEach(name => {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  /**
   * 便利旧属性
   * 1、拿到非事件属性和非子节点的属性
   * 2、拿到被删除的属性
   * 3、删除属性
   */
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(key => !(key in nextProps))
    .forEach(key => delete dom[key]);

  /**
   * 便利新属性
   * 1、拿到非事件属性和非子节点的属性
   * 2、拿到前后改变的属性
   * 3、添加属性
   */
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      dom[name] = nextProps[name];
    });

  /**
   * 便利新属性
   * 1、拿到on开头的事件属性
   * 2、拿到前后改变的事件属性
   * 3、为新增的事件属性添加监听
   */
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

完成了一系列对 dom 的操作,我们将新改变的 dom 渲染到页面,当 input 事件执行时,页面又会进行渲染,但此时会进入更新 fiber 树的逻辑,
alternate 指向之前的 fiber 节点进行复用,更快的执行 Update 操作,如图:

大功告成!

完整代码可以看我github

结论与总结 💢

结论

  1. 我们写的 JSX 代码被 babel 转化成了 React.createElement。
  2. React.createElement 返回的其实就是虚拟 DOM 结构。
  3. 虚拟 DOM 的调和和渲染可以简单粗暴的递归,但是这个过程是同步的,如果需要处理的节点过多,可能会阻塞用户输入和动画播放,造成卡顿。
  4. Fiber 是 16.x 引入的新特性,用处是将同步的调和变成异步的。
  5. Fiber 改造了虚拟 DOM 的结构,具有 父->第一个子, 子->兄, 子->父这几个指针,有了这几个指针,可以从任意一个 Fiber 节点找到其他节点。
  6. Fiber 将整棵树的同步任务拆分成了每个节点可以单独执行的异步执行结构。
  7. Fiber 可以从任意一个节点开始遍历,遍历是深度优先遍历,顺序是 父->子->兄->父,也就是从上往下,从左往右。
  8. Fiber 的调和阶段可以是异步的小任务,但是提交阶段( commit)必须是同步的。因为异步的 commit 可能让用户看到节点一个一个接连出现,体验不好。

总结

  • react hook 实现 ✖
  • react 合成事件 ✖
  • 还有很多没有实现 😤...

至此,谢谢各位在百忙之中点开这篇文章,希望对你们能有所帮助,如有问题欢迎各位大佬指正。工作原因这篇文章大概断断续续写了有一个月,工作上在忙一个基于 腾讯云TRTC+websocket 的小程序电话功能,有时间也会写成文章分享一下,当然 react 的实现文章也会继续

👋:跳转 github 欢迎给个 star,谢谢大家了

参考文献

🍑:手写系列-实现一个铂金段位的 React

🍑:build-your-own-react(强烈推荐)

🍑:手写 React 的 Fiber 架构,深入理解其原理

🍑:手写一个简单的 React

🍑:妙味课堂大圣老师 手写 react 的 fiber 和 hooks 架构

🍑:React Fiber 架构

🍑:手写一个简单的 React

到此这篇关于教你如何从 html 实现一个 react的文章就介绍到这了,更多相关 html 实现react内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 手把手带你用React撸一个日程组件

    目录 业务背景 使用技术 技术难点 设计思路

  • React+TypeScript进行项目构建案例讲解

        react项目构建可以很简单,但是如果是结合typescript,其实也不是很麻烦,官网也有很明确的说明.有两种办法:     1.直接构建带有typescript的react项目,我们需要增加额外的参数,模版不能使用默认的cra-template.而是使用cra-template-typescript. npx create-react-app tsreactdemo --template typescript           创建完成的成功提示与原来没有太大的区别,直接进入项目路

  • React中refs的一些常见用法汇总

    目录 什么是Refs 一.String 类型的 Refs 二.回调 Refs 三.React.createRef() 四.useRef 五.Refs 与函数组件 总结 什么是Refs Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素. Ref转发是一项将ref自动通过组件传递到子组件的技巧. 通常用来获取DOM节点或者React元素实例的工具.在React中Refs提供了一种方式,允许用户访问dom节点或者在render方法中创建的React

  • React 并发功能体验(前端的并发模式)

    React 是一个开源 JavaScript 库,开发人员使用它来创建基于 Web 和移动的应用程序,并且支持构建交互式用户界面和 UI 组件.React 是由 Facebook 软件工程师 Jordan Walke 创建,React 的第一个版本在七年前问世,现在,Facebook 负责维护.React框架自首次发布以来,React 的受欢迎程度直线飙升,热度不减. 2020 年 10 月,React 17 发布了,但令人惊讶的是--"零新功能".当然,这并不是真的表示没有任何新添加

  • 教你使用vscode 搭建react-native开发环境

    问题 代码没有提示: 许多刚接触RN开发的非前端同学,都会问"哪个编辑器有智能提示?"...而对于前端同学来说,现在的日子已经好很多了,要什么自行车. 低级代码错误: 这里的错误是指类似拼写错误,符号错误等.写完代码,跑起来各种报错,有时候费死劲的找,最后发现是个中文的分号问题. 解决办法 可选的方案大概有: 使用typescript: 直接使用有静态类型支持的js版本,but要再学习一套语法,而且我的代码都是ts写的,但很多好的公共库不是啊. 使用flow: 由于网络的原因,这个环境

  • React实现导入导出Excel文件

    目录 表示层 业务层 核心插件xlsx excel 导入 excel 导出 excel 导出插件(js-export-excel) 实现效果 结语 表示层 这里我是使用的是antd的Upload上传组件 引用antd部分代码 import { Button,Table,Upload } from 'antd'; <Upload {...props} fileList={state.fileList}> <Button type="primary" >Excel导

  • 基于visual studio code + react 开发环境搭建过程

    开发环境 windows 开发工具 visual studio code node 安装和 npm windows 安装node 可以直接在 node官网直接下载直接当作普通软件安装即可. 安装完成可以在控制台中运行node测试是否安装成功 win + r 输入 cmd ,直接在终端输入node -v 输出版本号及已经成功安装. 目前新版本的node自带npm(npm 是随同 node 一起安装的包管理工具).这里安装好了 node并且测试安装成功之后,可以继续在控制台输入 npm -v 检查是

  • Vue3.0中Ref与Reactive的区别示例详析

    目录 Ref与Reactive Ref Reactive Ref与Reactive的区别 shallowRef 与shallowReactive toRaw ---只修改数据不渲染页面 markRaw --- 不追踪数据 toRef --- 跟数据源关联 不修改UI toRefs ---设置多个toRef属性值 customRef ---自定义一个ref ref 捆绑页面的标签 总结 Ref与Reactive Ref Ref 用来创建基础类型的响应式数据,模板默认调用value显示数据.方法中修

  • 教你如何从 html 实现一个 react

    什么是 React React是一个简单的javascript UI库,用于构建高效.快速的用户界面.它是一个轻量级库,因此很受欢迎.它遵循组件设计模式.声明式编程范式和函数式编程概念,以使前端应用程序更高效.它使用虚拟DOM来有效地操作DOM.它遵循从高阶组件到低阶组件的单向数据流. 前言

  • 从零开始搭建一个react项目开发

    本文介绍了从零开始搭建一个react项目开发,分享给大家,具体如下: 1.npm init 生成 package.json 文件. 2.安装各种需要的依赖: npm install  --save react - 安装React. npm install  --save react-dom 安装React Dom,这个包是用来处理virtual DOM.这里提一下用React Native的话,这里就是安装react-native. npm install  --save-dev webpack

  • 使用webpack5从0到1搭建一个react项目的实现步骤

    前言 在这之前,每开始一个新项目我都是使用现有的脚手架,这非常便于快速地启动一个新项目,而且通用的脚手架通常考虑地更加全面,也有利于项目的稳定开发:不过对于一个小项目,根据需求自己搭建可能会更好,一方面小项目不需要脚手架那么丰富的功能,另一方面可以提高对项目的掌控度以方便后期的扩展. 这篇文章是在实践中总结的,具有实操性,读者可跟着一步步进行搭建,中间我会穿插一些原理,当然因为笔者的能力有限,不会特别深入. 预备知识 熟悉Javascript && HTML && CSS

  • 教你如何用Eclipse创建一个Maven项目

    一.Maven的安装与设置环境变量 想要创建一个Maven项目需要先安装Maven,并且设置好环境变量 1.去下载Maven 2.设置环境变量 新建变量MAVEN_HOME,值为Maven的目录X:\XXX\apache-maven-XXX 将%MAVEN_HOME%\bin添加到Path变量下 3.运行CMD,输入mvn -v后可以看到Maven的版本信息等则表示安装成功 二.创建Maven项目和依赖 2.1 创建Maven项目的两种方式 第一种创建方式:使用命令行手动创建 mvn arche

  • 教你如何使用Python开发一个钉钉群应答机器人

    前提 搭建钉钉应答机器人,需要先准备或拥有以下权限: 钉钉企业的管理员或子管理员(如果不是企业管理员,可以自己创建一个企业,很方便的) 有公网通信地址(内网穿透也可以): 钉钉群机器人开发文档:https://developers.dingtalk.com/document/app/overview-of-group-robots 创建「机器人」应用 登录「钉钉开发者后台」,选择「应用开发」--「企业内部开发」-- 「机器人」 输入好机器人的基本信息之后,就会生成创建一个「钉钉机器人」 我们的后

  • 使用 Rails API 构建一个 React 应用程序的详细步骤

    目录 后端:Rails API部分 前端:React部分 React组件 使用 axios 获取 API 数据 [51CTO.com快译]使用React创建项目时,动态数据无法保存的问题要怎么办呢?为此,我开始寻找一个充当备份的API来解决这一问题. 在本文中,我将介绍如何设置和构建一个以React作为前端的Rails API的一些要点,以帮助那些和我遇到一样问题的人. 本文计划使用Rails API作为后端,React作为前端,所以需要学习本文的人遵循同样的路径和步骤. 后端:Rails AP

  • 记一个React.memo引起的bug

    目录 一般memo用法: 问题描述 解决方法 方案一.使用useRef+forceUpdate方案 方案2.使用useCallback 总结 与PureComponent不同的是PureComponent只是进行浅对比props来决定是否跳过更新数据这个步骤,memo可以自己决定是否更新,但它是一个函数组件而非一个类,但请不要依赖它来“阻止”渲染,因为这会产生 bug. 一般memo用法: import React from "react"; function MyComponent(

  • 如何创建自己的第一个React 页面

    目录 Rract是啥? 背景 React脚手架 JSX是什么 Rract是啥? React 是用于构建用户界面的 JavaScript 库 构建用户界面. User Interface,对咱们前端来说,简单理解为:HTML 页面 javscrtipt库.不是框架,是库, react 全家桶才是框架 react 全家桶: react: 核心库react-dom: dom操作react-router:路由,redux:集中状态管理 背景 Rract最牛逼!全球使用最多 特点 声明式 用类似于html

  • 教你使用html+css制作一个3D立体相册

    目录 前言 一.先看效果 二.实现步骤 总结 前言 用css的transform属性做一个3D相册~~~值translate表示偏移:scale表示缩放:rotate就是转动. 一.先看效果 二.实现步骤 1.我们知道一个正方体有6个面,所以定义一个父亲元素然后定义6个子元素作为6个面.每个面放一张图片.里面q1表示前面,h2表示后面,以此类推,就是首拼音. <div class="baba"> <div class="q1"> <im

  • 手把手教你将Vim改装成一个IDE编程环境(图文) 吴垠

    By: 吴垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homepage: http://blog.csdn.net/wooin Copyright: 该文章版权由吴垠和他可爱的老婆小包子所有.可在非商业目的下任意传播和复制.对于商业目的下对本文的任何行为需经作者同意. 联系方式:lazy.fox.wu#gmail.com 1 写在前面 Linux下编程一直被诟病的一点是: 没有一个好用的IDE, 但是听说Linux牛人

随机推荐