我的编程空间,编程开发者的网络收藏夹
学习永远不晚

怎么用JS代码构建React

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

怎么用JS代码构建React

这篇文章主要讲解了“怎么用JS代码构建React”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用JS代码构建React”吧!

我们将构建什么功能?

  •  JSX

  •  函数组件

  •  类组件

  •  生命周期钩子函数

我们不会构建什么?

虚拟DOM

再次为了简单起见,我们不会在本文中实现我们自己的虚拟DOM,我们将使用 snabbdom ,有趣的是,Vue.js 虚拟DOM借鉴了它,你可以在这里读更多关于 snabbdom 的内容: https://github.com/snabbdom/s...

React Hooks

有些人可能对此感动失望,但是,一口吃不成一个胖子,我们需要一步一步来,因此让我们首先构建基本的东西,然后再在此基础上加以补充。我计划后续文章中在我们此次构建的内容之上,编写我们自己的 React Hooks 以及虚拟DOM,

可调试性

这是增加任何库或框架的复杂度的关键部分之一,由于我们只是出于娱乐目的而做,因此我们可以放心地忽略 React 提供的可调试性功能,例如 dev tools 和分析器。

性能和兼容性

我们不会过于关注我们的库的性能,我们只想构建能正常运行的库。让我们也不要费力地确保它可以在市场上的所有浏览器上使用,只有能够在某些现代浏览器上可以使用,那就已经很好了。

让我们开始动手

在开始之前,我们需要一个支持ES6,自动热更新的脚手架。我已经创建了一个非常基础的 webpack 脚手架,你可以进行克隆和设置: https://github.com/ameertheha...

JSX

JSX 是一个开放标准,不仅限于 React,我们可以在没有 React 的情况下使用它,它比你想象得还有容易。想要了解如何让我们的库支持 JSX ,我们首先需要看看在我们使用 JSX 时背后究竟发生了什么。

const App = (      <div>          <h2 className="primary">QndReact is Quick and dirty react</h2>          <p>It is about building your own React in 90 lines of JavsScript</p>      </div>  );  // 上面的 jsx 被转换成下面这样:    var App = React.createElement(      "div",      null,      React.createElement(          "h2",          {              className: "primary"          },          "QndReact is Quick and dirty react"      ),      React.createElement(          "p",          null,          "It is about building your own React in 90 lines of JavsScript"      )  );

正如你看到的,每个 JSX 元素都通过 @babel/plugin-transform-react-jsx 插件转换为了 React.createElement(...) 函数调用的形式,你可以在这里使用 JSX 进行更多的转换

为了使上述转换运行正常,在编写 JSX 时,你需要引入 React,你就是为什么当你不引入 React 时,编写 JSX 会出现错误的原因。 @babel/plugin-transform-react-jsx 插件已经添加在了我们的项目依赖中,下面我们先安装一下依赖

npm install

把项目的配置增加到 .babelrc 文件中:

{      "plugins": [          [              "@babel/plugin-transform-react-jsx",              {                  "pragma": "QndReact.createElement", // default pragma is React.createElement                  "throwIfNamespace": false // defaults to true              }          ]      ]  }

此后,只要 Babel 看到 JSX ,它就会调用 QntReact.createElement(...),但是我们还未定义此函数,现在我们将其写到 class="lazy" data-src/qnd-react.js 中。

const createElement = (type, props = {}, ...children) => {      console.log(type, props, children);  };  // 像 React.createElement 一样导出  const QndReact = {      createElement  };  export default QndReact;

我们在控制台打印出了传递给我们的 type 、 props、 children。为了测试我们的转换是否正常,我们可以在 class="lazy" data-src/index.js 中编写一些 JSX 。

// QndReact 需要被引入  import QndReact from "./qnd-react";  const App = (      <div>          <h2 className="primary">              QndReact is Quick and dirty react          </h2>          <p>It is about building your own React in 90 lines of JavsScript</p>      </div>  );

启动项目: npm start,在浏览器输入localhost:3000,现在你的控制台看起来应该与下图类似:

怎么用JS代码构建React

根据以上信息,我们可以使用 snabbdom 创建我们内部的 虚拟DOM节点 ,然后我们才能将其用于我们的协调(reconciliation) 过程,可以使用如下的命令安装 snabbdom:

npm install snabbdom

当 QndReact.createElement(...) 被调用时吗,创建和返回 虚拟DOM节点。

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      return h(type, { props }, children);  };  const QndReact = {      createElement  };  export default QndReact;

很好,现在我们可以解析 JSX 并创建自己的虚拟DOM节点,但是仍然无法在浏览器中呈现出来。为此,我们在 class="lazy" data-src/qnd-react-dom.js 添加一个 render 方法。

//class="lazy" data-src/qnd-react-dom.js  //React.render(<App />, document.getElementById('root'));  const render = (el, rootElement) => {      //将el渲染到rootElement的逻辑  }  const QndReactDom = {      render  }

与其我们自己去处理将元素放到 DOM 上的繁重工作,不如让 snabbdom 去处理。为此我们可以引入模块去初始化 snabbdom。snabbdom 中的模块可以看做是插件,可以支持 snabbdom 做更多的事。

//class="lazy" data-src/qnd-react-dom.js  import * as snabbdom from 'snabbdom';  import propsModule from 'snabbdom/modules/props';  const reconcile = snabbdom.init([propsModule]);  const render = (el, rootDomElement) => {      //将el渲染到rootElement      reconcile(rootDomElement, el);  }  const QndReactDom = {      render  }  export default QndReactDom;

我们使用这个新的 render 函数去 class="lazy" data-src/index 中去做一些魔法。

//class="lazy" data-src/index.js  import QndReact from "./qnd-react";  import QndReactDom from './qnd-react-dom';  const App = (      <div>          <h2 className="primary">              QndReact is Quick and dirty react          </h2>          <p>It is about building your own React in 90 lines of JavsScript</p>      </div>  );  QndReactDom.render(App, document.getElementById('root'));

瞧,我们的JSX已经可以渲染到屏幕上了。

怎么用JS代码构建React

等下,这个有一个小问题,当我们两次调用 render 时,我们会在控制台看到一些奇怪的错误(译者注: 可以在 index.js 中多次调用 render,查看控制台错误),背后的原因是我们只有在第一次渲染时,可以在真实的DOM节点上调用 reconcile 方法,然后,我们应该在之前返回的虚拟DOM节点上调用。

//class="lazy" data-src/qnd-react-dom.js  import * as snabbdom from 'snabbdom';  import propsModule from 'snabbdom/modules/props';  const reconcile = snabbdom.init([propsModule]);  let rootVNode;  //QndReactDom.render(App, document.getElementById('root'))  const render = (el, rootDomElement) => {      if(rootVNode == null) {          //第一次调用 render 时          rootVNode = rootDomElement;      }      rootVNode = reconcile(rootVNode, el);  }  const QndReactDom = {      render  }  export default QndReactDom;

很开心,我们的应用程序中有一个能正常工作的 JSX 渲染,现在让我们开始渲染一个函数组件,而不仅仅是一些普通的 HTML。

让我们向 class="lazy" data-src/index.js 添加一个 Greeting 函数组件,如下所示:

//class="lazy" data-src/index.js  import QndReact from "./qnd-react";  import QndReactDom from './qnd-react-dom';  const Greeting = ({ name }) => <p>Welcome {name}!</p>;  const App = (      <div>          <h2 className="primary">              QndReact is Quick and dirty react          </h2>          <p>It is about building your own React in 90 lines of JavsScript</p>          <Greeting name={"Ameer Jhan"} />      </div>  );  QndReactDom.render(App, document.getElementById('root'));

此时,在控制台会出现以下错误:

怎么用JS代码构建React

我们可以在 QndReact.createElement(...) 方法中打印出数据看一下原因。

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      console.log(type, props, children);      return h(type, { props }, children);  };  ...

怎么用JS代码构建React

如果可以看到,函数组件传递过来的 type 是一个JS函数。如果我们调用这个函数,就能获得组件希望渲染的 HTML 结果。

我们根据 type 参数的类型,如果是函数类型,我们就调用这个函数,并将 props 作为参数传给它,如果不是函数类型,我们就当作普通的 HTML 元素处理。

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };  const QndReact = {      createElement  };  export default QndReact;

欢呼!我们的函数组件已经可以正常工作了。

怎么用JS代码构建React

我们已经完成了很多,让我们深吸一口气,喝杯咖啡,因为我们已经差不多实现了 React,不过我们还需要攻克类组件。

我们首先在 class="lazy" data-src/qnd-react.js 中创建 Component 基类:

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };  class Component {      constructor() { }      componentDidMount() { }      setState(partialState) { }      render() { }  }  const QndReact = {      createElement,      Component  };  export default QndReact;

现在我们在 class="lazy" data-src/counter.js 中编写我们的第一个 Counter 类组件:

//class="lazy" data-src/counter.js  import QndReact from './qnd-react';  export default class Counter extends QndReact.Component {      constructor(props) {          super(props);          this.state = {              count: 0          }      }      componentDidMount() {          console.log('Component mounted');      }      render() {          return <p>Count: {this.state.count}</p>      }  }

是的,我知道我们尚未在计数器中实现任何逻辑,但是别担心,一旦我们的状态管理系统运行正常,我们就会添加这些内容。现在,让我们尝试在 class="lazy" data-src/index.js 中渲染它。

//class="lazy" data-src/index.js  import QndReact from "./qnd-react";  import QndReactDom from './qnd-react-dom';  import Counter from "./counter";  const Greeting = ({ name }) => <p>Welcome {name}!</p>;  const App = (      <div>          <h2 className="primary">              QndReact is Quick and dirty react          </h2>          <p>It is about building your own React in 90 lines of JavsScript</p>          <Greeting name={"Ameer Jhan"} />          <Counter />      </div>  );  QndReactDom.render(App, document.getElementById('root'));

和料想中的一样,又又又报错了。

怎么用JS代码构建React

上面的错误看起来是不是很熟悉,当你尝试使用类组件而不集成自 React.Component 时,可能遇到过以上错误。要知道为什么会这样,我们可以在 React.createElement(...) 中添加一个 console.log,如下所示:

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      console.log(typeof (type), type);      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };

我们来看看控制台打印了什么内容。

怎么用JS代码构建React

你可以看出 Counter 的 type 类型也是函数,这是因为 Babel 会将 ES6 类转换为普通的 JS 函数,那么我们该如何类组件的情况呢。其实,我们可以在我们的 Component 基类中添加一个静态属性,这样我们利用该属性去检查 type 参数是否是一个类。React 中也是相同的处理逻辑,你可以阅读 Dan的博客

//class="lazy" data-src/qnt-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      console.log(typeof (type), type);      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  }; class Component {      constructor() { }      componentDidMount() { }      setState(partialState) { }      render() { }  }  //给 Component 组件添加静态属性来区分是函数还是类   Component.prototype.isQndReactClassComponent = true;  const QndReact = {      createElement,      Component  };  export default QndReact;

现在,我们在 QndReact.createElement(...) 中增加一些代码来处理类组件。

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      console.log(type.prototype);            if (type.prototype && type.prototype.isQndReactClassComponent) {          const componentInstance = new type(props);          return componentInstance.render();      }      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };  class Component {      constructor() { }      componentDidMount() { }      setState(partialState) { }      render() { }  }  //给 Component 组件添加静态属性来区分是函数还是类   Component.prototype.isQndReactClassComponent = true;  const QndReact = {      createElement,      Component  };  export default QndReact;

现在,我们的类组件已经能够渲染到浏览器上了:

怎么用JS代码构建React

我们向类组件中增加 state,在此之前,我们需要知道,每次调用 this.setState({}) 时,如何更新 DOM 的责任是 react-dom 包,而不是 React 的责任。这是为了使 React 的核心部分,例如Component 类与平台分离,从而提升代码的可重用性。即在 ReactNative 中,你也可以使用同样的 Component 类,react-native 负责如何更新UI。你可能会问自己:当调用 this.setState(...) 时,React 如何知道该怎么做,答案就是 react-dom 通过在 React 上设置了一个 __updater 属性与 React 进行通信。Dan 对此也有出色的文章,你可以点击阅读。现在让我们在 QndReactDom 中为 QndReact 添加 __updater 属性。

//class="lazy" data-src/qnd-react-dom.js  import QndReact from './qnd-react';  import * as snabbdom from 'snabbdom';  import propsModule from 'snabbdom/modules/props';  ...  //QndReactDom 告诉 QndReact 如何更新 DOM  QndReact.__updater = () => {      //当调用 this.setState 的时候更新 DOM 逻辑  }

无论何时我们调用 this.setState({...}),我们都需要比较组件的 oldVNode 和在组件上调用了 render 方法之后生成的 newVNode。为了进行比较,我们在类组件上添加 __vNode 属性,以维护该组件当前的 VNode 实例。

//class="lazy" data-src/qnd-react.js  ...  const createElement = (type, props = {}, ...children) => {            if (type.prototype && type.prototype.isQndReactClassComponent) {          const componentInstance = new type(props);          componentInstancecomponentInstance.__vNode = componentInstance.render();          return componentInstance.__vNode;      }      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };  ...

现在我们来在 Component 的基类中实现 setState 方法。

//class="lazy" data-src/qnd-react.js  ...  class Component {      constructor() { }      componentDidMount() { }      setState(partialState) {           this.state = {              ...this.state,              ...partialState          }          //调用 QndReactDom 提供的 __updater 方法          QndReact.__updater(this);      }      render() { }  }  ...

处理 QndReactDom 中的 __updater 方法。

//class="lazy" data-src/qnd-react-dom.js  ...  QndReact.__updater = (componentInstance) => {      //当调用 this.setState 的时候更新 DOM 逻辑      //获取在 __vNode 上存储的 oldVNode      const oldVNode = componentInstance.__vNode;      //获取 newVNode      const newVNode = componentInstance.render();      //更新 __vNode      componentInstance.__vNode = reconcile(oldVNode, newVNode);  }  ...  export default QndReactDom;

OK,我们在 Counter 组件中增加 state 来检验我们的 setState 实现是否生效。

//class="lazy" data-src/counter.js  import QndReact from './qnd-react';  export default class Counter extends QndReact.Component {      constructor(props) {          super(props);          this.state = {              count: 0          }          // update the count every second          setInterval(() => {              this.setState({                  count: this.state.count + 1              })          }, 1000);      }      componentDidMount() {          console.log('Component mounted');      }      render() {          return <p>Count: {this.state.count}</p>      }  }

太棒啦,现在 Counter 组件运行情况与我们预期完全一致。

我们继续添加 componentDidMount 的生命周期钩子函数。 Snabbdom 提供了一些钩子函数,通过他们,我们可以知道真实DOM上面是否有添加,删除或是更新了虚拟DOM节点,你可以在此处了解更多信息。

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {            if (type.prototype && type.prototype.isQndReactClassComponent) {          const componentInstance = new type(props);          componentInstancecomponentInstance.__vNode = componentInstance.render();          return componentInstance.__vNode;          //增加钩子函数(当虚拟DOM被添加到真实DOM节点上时)          componentInstance.__vNode.data.hook = {              create: () => {                  componentInstance.componentDidMount()              }          }      }      //如果是函数组件,那么调用它,并返回执行结果      if (typeof (type) == 'function') {          return type(props);      }      return h(type, { props }, children);  };  ...  export default QndReact;

至此,我们已经在类组件上支持了 componentDidMount 生命周期钩子函数。

结束之前,我们再添加下事件绑定的支持。为此,我们可以在 Counter 组件中增加一个按钮,点击的时候,计数器的数字增加。请注意,我们遵循的是基于常规的JS事件命名约定,而非基于 React,即双击事件使用 onDblClick,而非 onDoubleClick。

import QndReact from './qnd-react';  export default class Counter extends QndReact.Component {      constructor(props) {          super(props);          this.state = {              count: 0          }      }      componentDidMount() {          console.log('Component mounted');      }      render() {          return (              <div>                  <p>Count: {this.state.count}</p>                  <button onClick={() => this.setState({                      count: this.state.count + 1                  })}>Increment</button>              </div>          )      }  }

上面的组件不会正常工作,因为我们没有告诉我们的 VDom 如何去处理它。首先,我们给 Snabdom 增加事件监听模块。

//class="lazy" data-src/qnd-react-dom.js  import QndReact from './qnd-react';  import * as snabbdom from 'snabbdom';  import propsModule from 'snabbdom/modules/props';  import eventlistenersModule from 'snabbdom/modules/eventlisteners';  const reconcile = snabbdom.init([propsModule, eventlistenersModule]);  ...

Snabdom 希望将文本属性和事件属性作为两个单独的对象,我们我们需要这样做:

//class="lazy" data-src/qnd-react.js  import { h } from 'snabbdom';  const createElement = (type, props = {}, ...children) => {      ...      let dataProps = {};      let eventProps = {};      for (let propKey in props) {          // event 属性总是以 `on` 开头          if (propKey.startsWith('on')) {              const event = propKey.substring(2).toLowerCase();              eventProps[event] = props[propKey];          } else {              dataProps[propKey] = props[propKey];          }      }      return h(type, { props: dataProps, on: eventProps }, children);  };  ...

现在当我们点击 Counter 组件的按钮的时候,计数器加1。

怎么用JS代码构建React

感谢各位的阅读,以上就是“怎么用JS代码构建React”的内容了,经过本文的学习后,相信大家对怎么用JS代码构建React这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

怎么用JS代码构建React

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

React怎么构建小程序

这篇文章主要介绍“React怎么构建小程序”,在日常操作中,相信很多人在React怎么构建小程序问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”React怎么构建小程序”的疑惑有所帮助!接下来,请跟着小编一起来
2023-06-22

怎么构建自己的react hooks

这篇文章主要介绍怎么构建自己的react hooks,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1. 常用的一个 hooks官方中提供了几个内置的钩子,我们简单了解下他们的用法。1.1 useState: 状态钩子
2023-06-15

怎么用VSCode调试React Vue代码

这篇文章主要介绍了怎么用VSCode调试React Vue代码的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么用VSCode调试React Vue代码文章都会有所收获,下面我们一起来看看吧。用 VSCode
2023-07-02

js代码怎么在html中使用

在 html 中使用 javascript 代码可通过以下方式:外部脚本:使用 内联脚本:使用 // javascript 代码脚本位置:通常放置在 末尾事件处理程序:使用 onclick 属性范围和命名:遵循 html 的规则如何在
js代码怎么在html中使用
2024-05-16

易语言怎么调用JS代码

在易语言中调用JS代码可以通过使用Web控件来实现。具体步骤如下:在易语言程序中添加一个Web控件,例如Web控件的名字为Web1。设置Web控件的URL为一个包含JS代码的网页地址,例如可以是一个本地的HTML文件或者是一个在线的JS代码
易语言怎么调用JS代码
2024-03-08

html怎么运行js代码

可以通过内联脚本、外部脚本或事件处理程序在 html 中运行 js 代码。内联脚本将 js 代码直接嵌入 html 页面,外部脚本将代码放在外部文件中并链接到页面,而事件处理程序将 js 代码附加到 html 元素上的事件。如何使用 HTM
html怎么运行js代码
2024-05-16

php怎么替换js代码

php替换js代码的方法:1、创建一个PHP示例文件;2、利用php中的preg_replace正则匹配函数替换过滤掉网页中的js代码即可。
2019-05-21

JS代码编译器中Monaco怎么用

这篇文章给大家分享的是有关JS代码编译器中Monaco怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言我的需求是可以语法高亮、函数提示功能、自动换行、代码折叠MonacoMonaco是微软家的,支持的语言
2023-06-15

怎样用Python代码构建最小的区块链

怎样用Python代码构建最小的区块链,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。尽管一些人认为区块链是一个等待问题的解决方案,但毫无疑问,这种新技术是计算机的奇迹。但是,区
2023-06-17

React Fiber树是怎么构建与更新的

这篇文章主要讲解了“React Fiber树是怎么构建与更新的”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“React Fiber树是怎么构建与更新的”吧!为什么需要 fiberLin Cl
2023-07-04

编程热搜

目录