1 Flutter实例大全
547 2023-04-03 03:13:47
#react
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);
抛去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;}
现在,我们开始实现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
进行包裹,具体缘由在后面说。
创建好虚拟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);}
比较难理解的就两个
element.props.children.forEach((child) => render(child, dom))
element: ReturnType<typeof createElement>
根据以下两种写法,我们来回答刚刚前面问的为什么在child !== 'object'
时对ReactNode
进行包装?
因为这样可以确保返回的类型children
的ReactElement[]
,而获取该类型是为了方便以下函数的递归。
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);