bar )const container = document.getElementById("root")ReactDOM.render(element, container)通过babel把JSX转换成JS" /> bar )const container = document.getElementById("root")ReactDOM.render(element, container)通过babel把JSX转换成JS" />

1. createElemnt && render(草稿)

1. createElemnt && render(草稿)

#react

1. createElemnt && render

const element = (  <div id="foo">    <a>bar</a>    <b />  </div>)const container = document.getElementById("root")ReactDOM.render(element, container)

通过babel把JSX转换成JS

删除冗余信息得到

const element = React.createElement(  "div",  {    id: "foo",  },  React.createElement("a", null, "bar"),  React.createElement("b", null));const container = document.getElementById("root");ReactDOM.render(element, container);

(React)Node 和(React)Element

抛去JSX及虚拟DOM的概念不谈,我们先来回顾下HTML中真实的DOM是怎么划分的呢?

具体的到 Node properties: type, tag and contents看,这个网站一级棒!

那我们现在如果需要做个虚拟DOM,即使用对象来映射上面的DOM关系,你会怎么思考呢?

看图知道个大概意思就好了,图的细节不重要,细节在代码中呈现,如下代码:

type ReactText = string | number;type ReactChild = ReactElement | ReactText;interface ReactNodeArray extends Array<ReactNode> {}type ReactNode = ReactChild | ReactNodeArray | boolean | null | undefined;interface Attributes extends Record<string, any> {  children?: ReactNode;}interface ReactElement<P extends Attributes = any, T = string> {  type: T;  props: P;}

createElemnt

现在,我们开始实现React.createElement

function createTextElement(text: ReactText): ReactElement {  return {    type: "TEXT_ELEMENT",    props: {      nodeValue: text,      children: []    }  };}function createElement(  type: string,  props: Attributes | null,  ...children: ReactChild[]) {  return {    type,    props: {      ...props,      children: children.map((child) =>        typeof child === "object" ? child : createTextElement(child)      )    }  };}console.log(`==============>createElement("div", null, a)`);console.log(createElement("div", null, "a"));// { type: 'div', props: { children: [ 'a' ] } }console.log(`==============>createElement("div", null, a, b)`);console.log(createElement("div", null, "a", "b"));// { type: 'div', props: { children: [ 'a', 'b' ] } }

函数作用就是返回一个ReactElement类型,比较困惑的可能就是以下这段函数

children.map((child) =>  typeof child === "object" ? child : createTextElement(child));

根据TS类型判断我们很容易知道child的类型是ReactChild

type ReactText = string | number;type ReactChild = ReactElement | ReactText;interface ReactElement<P extends Attributes = any, T = string> {  type: T;  props: P;}

所以当child !== "object"的时候,child的类型就是ReactText,即string或者number

为了后面的代码简单和统一,我们使用createTextElement进行包裹,具体缘由在后面说。

render

创建好虚拟DOM后,我们就需要把这些DOM映射到真实的环境中,这里我们仅实现ReactDOM,即web环境。

哈哈!别的环境我也不会,又没有文章可以抄,唉!希望有大佬写个react native借我抄

function render(  element: ReturnType<typeof createElement>,  container: HTMLElement | null): void {  if (!container) {    console.error("获取不到容器,请确保根节点是否存在");    return;  }  // 把虚拟DOM,即ReactElement 转换成真实的dom  const dom = document.createElement(element.type);  // 递归创建真实dom  element.props.children.forEach((child) => render(child, dom));  // 把dom插入到页面对应的容器中  container.appendChild(dom);}

比较难理解的就两个

  1. 递归: element.props.children.forEach((child) => render(child, dom))
  2. 类型: element: ReturnType<typeof createElement>

根据以下两种写法,我们来回答刚刚前面问的为什么在child !== 'object'时对ReactNode进行包装?

因为这样可以确保返回的类型childrenReactElement[],而获取该类型是为了方便以下函数的递归。

  element.props.children.forEach((child) => render(child, dom));

在理解递归后,我们需要对上面的代码进行分类处理

const dom =      element.type === "TEXT_ELEMENT"? document.createTextNode(""): document.createElement(element.type);

还有把属性添加到dom上

 const dom =    element.type === "TEXT_ELEMENT"      ? document.createTextNode("")      : document.createElement(element.type);  const isProperty = (key: Key) => key !== "children";  Object.keys(element.props)    .filter(isProperty)    .forEach((name) => {      // @ts-ignore      dom[name] = element.props[name];    });

主呀!感谢万能的@ts-ignore

然后代码就可以跑了!

全部代码

codesandbox

/* 类型-------------------------------------------------------------------------- */type Key = string | number;type ReactText = string | number;type ReactChild = ReactElement | ReactText;interface ReactNodeArray extends Array<ReactNode> {}type ReactNode = ReactChild | ReactNodeArray | boolean | null | undefined;interface Attributes extends Record<string, any> {  children?: ReactNode;}interface ReactElement<P extends Attributes = any, T = string> {  type: T;  props: P;}/* 源码实现-------------------------------------------------------------------------- */function createTextElement(text: ReactText): ReactElement {  return {    type: "TEXT_ELEMENT",    props: {      nodeValue: text,      children: []    }  };}function createElement(  type: string,  props: Attributes | null,  ...children: ReactChild[]) {  return {    type,    props: {      ...props,      children: children.map((child) =>        typeof child === "object" ? child : createTextElement(child)      )    }  };}function render(  element: ReturnType<typeof createElement>,  container: Node | null): void {  if (!container) {    console.error("获取不到容器,请确保根节点是否存在");    return;  }  // 把虚拟DOM,即ReactElement 转换成真实的dom  const dom =    element.type === "TEXT_ELEMENT"      ? document.createTextNode("")      : document.createElement(element.type);  const isProperty = (key: Key) => key !== "children";  Object.keys(element.props)    .filter(isProperty)    .forEach((name) => {      // @ts-ignore      dom[name] = element.props[name];    });  // 递归创建真实dom  element.props.children.forEach((child) => render(child, dom));  // 把dom插入到页面对应的容器中  container.appendChild(dom);}const React = {  createElement};const ReactDom = {  render};/*用例 -------------------------------------------------------------------------- */const element = (  <div style="background: salmon">    <h1>Hello World</h1>    <h2 style="text-align:right">——忘尘</h2>  </div>);const rootElement = document.getElementById("root");ReactDom.render(element, rootElement);
免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部