【系列 2】react 运行函数组件

434 阅读2分钟

react 运行函数组件

import React from './react';
import ReactDOM from './react-dom';

function FunctionComponent(props){
  return <div className="title" style={{ color: 'red' }}>
    <h1>{props.name}</h1>
    <h3>{props.children}</h3>
  </div>
}

ReactDOM.render(
  <FunctionComponent name="hello">world</FunctionComponent>,
  document.getElementById('root')
);

1. jsx 转成 js 代码

由于 js 不认识 jsx 代码,所以需要将下面 jsx 语法转化成 js 代码

function FunctionComponent(props){
  return <div className="title" style={{ color: 'red' }}>
    <h1>{props.name}</h1>
    <h3>{props.children}</h3>
  </div>
}

点击此 jsx to react 复制上述代码进去即可转化成 js 代码, 代码如下:

function FunctionComponent(props) {
  return React.createElement("div", {
        className: "title",
        style: {
          color: 'red'
        }
      },
      React.createElement("h1", null, props.name),    // 第二个参数及之后都是子元素
      React.createElement("h3", null, props.children)
  );
}

下面组件 jsx 代码

<FunctionComponent name="hello">world</FunctionComponent>

也会转化成:

React.createElement(FunctionComponent, {  // 区别就是 参数1 变成函数
  name: "hello"
}, "world");

在 create-react-app 项目中,我们通过 react-scripts start 可以运行 react 项目是因为 react-scripts 内配置的 webpack 中的插件 @babel/plugin-syntax-jsx 将 jsx 转化成了 js 代码。

2. React.createElement 接收函数参数

现在知道为什么没有使用 react 都需要 import React from 'react'; 引入了吧, 因为 jsx 转 js 后的代码会用到 react。

执行如下方法会生成什么呢?

React.createElement(FunctionComponent, {  // 区别就是 参数1 变成函数
  name: "hello"
}, "world");

执行如上方法,其实就是执行如下内容:

function FunctionComponent(props) {
  return React.createElement("div", {
        className: "title",
        style: {
          color: 'red'
        }
      },
      React.createElement("h1", null, props.name),    // 第二个参数及之后都是子元素
      React.createElement("h3", null, props.children)
  );
}

React.createElement(FunctionComponent, {  // 区别就是 参数1 变成函数
  name: "hello"
}, "world");

执行如下方法:

ReactDOM.render(
  <FunctionComponent name="hello">world</FunctionComponent>,
  document.getElementById('root')
);

其实就是执行如下内容:

function FunctionComponent(props) {
  return React.createElement("div", {
        className: "title",
        style: {
          color: 'red'
        }
      },
      React.createElement("h1", null, props.name),    // 第二个参数及之后都是子元素
      React.createElement("h3", null, props.children)
  );
}

ReactDOM.render(
    React.createElement(FunctionComponent, {  // 区别就是 参数1 变成函数
      name: "hello"
    }, "world"),
    document.getElementById('root')
)

可见 ReactDOM.createElement 方法还是【系列 1】 那个方法返回 vdom,
需要改变的只是 React.render 的 参数1 需要兼容传递函数 vdom

修改代码如下:

function render(vdom, container) {
  mount(vdom, container);
}
export function mount(vdom, container) {
  const newDOM = createDOM(vdom);
  container.appendChild(newDOM);
}
export function createDOM(vdom) {
  const { type, props } = vdom;
  let dom;
  if (type === REACT_TEXT) {
    dom = document.createTextNode(props.content);
  
// + 添加一个是函数的判断
  } else if (typeof type === "function") {
    return mountFunctionComponent(vdom);
  } else {
    dom = document.createElement(type);
  }
  if (props) {
    updateProps(dom, {}, props);
    if (typeof props.children == "object" && props.children.type) {
      mount(props.children, dom);
    } else if (Array.isArray(props.children)) {
      reconcileChildren(props.children, dom);
    }
  }
  vdom.dom = dom;
  return dom;
}

// + 添加一个方法就是执行这个函数组件 传递组件上的参数
function mountFunctionComponent(vdom) {
  const { type, props } = vdom
  const renderVdom = type(props)  // type 就是 FunctionComponent 组件函数
  return createDOM(renderVdom)
}

function updateProps(dom, oldProps = {}, newProps = {}) {
  for (let key in newProps) {
    if (key === "children") {
      continue;
    } else if (key === "style") {
      let styleObj = newProps[key];
      for (let attr in styleObj) {
        dom.style[attr] = styleObj[attr];
      }
    } else {
      dom[key] = newProps[key];
    }
  }
  for (let key in oldProps) {
    if (!newProps.hasOwnProperty(key)) {
      dom[key] = null;
    }
  }
}
function reconcileChildren(childrenVdom, parentDOM) {
  for (let i = 0; i < childrenVdom.length; i++) {
    let childVdom = childrenVdom[i];
    mount(childVdom, parentDOM);
  }
}
const ReactDOM = {
  render,
};

export default ReactDOM;

最终效果:

此时打开浏览器我们就看见了 hello world

image.png

本系列代码链接
GitHubgithub.com/shunyue1320…
Giteegitee.com/shunyue/min…