构建JSX + DOM库,第3部分
在上一部分中,我们遇到了一个挑战:更新 H1
组件 样式
也是
处理这个问题的最明显的地方是 给予
。到目前为止,我们只关注渲染根元素并忽略了它的子元素。添加一个递归调用其余子节点渲染的循环对我们来说是神奇的:
功能 给予(元件) { 如果 (propsStore。具有(元件)) 返回 updateProps(元件) 对于 (让 儿童 的 元件。的childNodes) { 给予(儿童) } }
我们用 给予
因为我们无法保证子元素是由我们的库创建或管理的。还有,打电话 给予
确保我们也称孩子为孩子。
要将此更改用于库,我们还需要更新应用程序代码。使用白色文本颜色为红色背景可能会很好地工作
常量 REF = ( <DIV 风格={() => `background-color:${props.dark? 'red':'wheat'};填充:5px;`}> <H1 风格={() => `color:${props.dark? 'white':'#333'};`}> 你好,世界 H1> <按键 的onclick ={换颜色}>换颜色按键> DIV> )
结果我们的 H1
元素现在应该更新:
它做了:)在CodePen示例中我添加了一些 的console.log
至 updateProps
这使得现在更容易看到所有应用的突变。您已经可以找到一些改进:例如,尽管没有有效的改变,小麦背景颜色设置了两次。目前我们就是这样(当然,你可以这样做)。
现在忽略优化的一个很好的理由是我们还没有完整的功能集。我们拥有的代码越多,实现新功能就越难。并且优化本身往往很棘手:在进行全面优化之前进行测试是有意义的。
在这一点上,我们仍然处于添加所有基本功能的早期阶段,我们需要具有“完整”可用的类似React的库。
那么,我们下一步该去哪儿?它确实痒很多,继续消除最后的烦恼 渲染(REF)
并且看起来像我们当前的应用程序代码的“功能完整”,它可以真正成为一个独立的组件,应用程序端开发人员需要最少的样板。
但目前组件实际上存在问题。我们抽象时可以揭示这一点 H1
它自己的组件:
功能 你好,世界(道具) { 返回 ( <H1 风格={() => `color:${props.dark? 'white':'#333'};`}> 你好,世界 H1> ) } //并在Component中用...替换h1 <你好,世界 暗={() => 道具。暗} />
我们的文字总是白的为什么?如果我们调试 props.dark
内 你好,世界
,我们注意到一件事:是一个功能。这意味着它会被触及而不是被管理。我们必须将它作为函数传递给组件才能更新 暗
值。如果由于我们的限制而不使用功能来帮助我们,它将永远不会得到更新。
管理组件
我们的组件抽象显然无法完成任务。当我们调查 DOM
我们注意到我们省略了组件的所有道具管理: if(isFn)propsStore.set(element,props)
。此外,我们当前的所有呈现代码都只假定本机DOM节点。
我们还有一个我们想要的功能:将组件的props作为输入传递给属性函数。我们喜欢这个的一个原因是它允许优化那些功能(例如memoize),这在功能执行成本高的情况下会很好。
我们有一些要求来管理组件:
- 有些东西需要链接元素及其相关组件。
- 我们需要在某处存储组件道具,以便我们可以传递它们。
首先,我们不能将组件的功能用作参考,因为我们可能多次使用相同的组件。为了解决这个问题,我们可以退后一步。是什么 DOM
需要输出?一个有效的DOM节点。我们可以使用哪些东西来盘点其他DOM节点吗?
片段片段是特殊的DOM节点,因为它们只存在于树的顶部。片段不能作为子节点存在:它们的子节点总是自动添加,并从片段中删除。
第二点现在更容易回答:我们可以使用现有的 propsStore
并使用片段作为参考。我们现在可以开始实现一个代码,它将元素标记为属于一个组件,这样我们就可以将组件的props作为这些元素的属性函数的输入。
呵呵。这有些复杂我们现在将对现有的库方法进行大量更改,并有一些新的内部帮助函数可供查看。
改变为 DOM
从这里开始,当代码量开始超过一个文件时,我将从Codepen切换到Codesandbox。代码库部分将继续执行 library.js
还会 出口
两种方法: DOM
和 给予
。
在完成这些方法之前,我们添加了两个新的WeakMaps:
常量 componentPropsStore = 新 WeakMap() 常量 parentComponents = 新 WeakMap()
现在让我们继续看看我们有什么新东西。
出口 功能 DOM(零件, 道具, ...孩子) { 道具 = { ...道具 } 常量 isComponent = 类型 零件 === '功能' //创建输出DOM元素 常量 元件 = isComponent ? 文献。createDocumentFragment() : 文献。的createElement(零件) 如果 (isComponent) { //记得原创道具 componentPropsStore。组(元件, 道具) //创建获取函数调用更新的新对象 常量 exposedProps = updateComponentProps({}, 道具) //像普通元素道具一样存储 propsStore。组(元件, exposedProps) //调用组件来创建它的输出 元件。使用appendChild(零件(exposedProps)) //将我们创建的每个DOM节点标记到此组件 对于 (让 儿童 的 元件。的childNodes) { setParentComponent(儿童, 元件, exposedProps) } } 其他 { propsStore。组(元件, 道具) updateProps(元件) } //在这里未触及,所以我们在某些时候会遇到问题:) 返回 孩子。降低(功能(埃尔, 儿童) { 如果 (儿童 的instanceof 节点) 埃尔。使用appendChild(儿童) 其他 埃尔。使用appendChild(文献。一个createTextNode(串(儿童))) 返回 埃尔 }, 元件) }
一个功能,我们已经引入了两个新功能
-
updateComponentProps
管理调用函数并更新结果状态,然后将其暴露给组件 -
setParentComponent
将被调用组件的所有子项标记为该组件,包括另一个组件
但我们尚未准备好对现有方法进行更改。
改变为 给予
出口 功能 给予(元件, 分段, componentProps) { 如果 (propsStore。具有(元件)) 返回 //检测父组件,以便我们可以注意到上下文是否发生了变化 常量 亲 = parentComponents。得到(元件) 如果 (亲 == 分段) { //上下文发生了变化 分段 = 亲 //通过调用函数来更新组件道具 常量 道具 = componentPropsStore。得到(分段) 如果 (道具) { componentProps = updateComponentProps( propsStore。得到(分段) 道具, componentProps ) } } //我们现在在这里传递相关的componentProps updateProps(元件, componentProps) 对于 (让 儿童 的 元件。的childNodes) { 给予(儿童, 分段, componentProps) } }
这里我们在渲染时更新组件道具。我们不是一次又一次地创建道具,而是仅在组件发生变化时才开始工作。
改变为 updateProps
这里发生的变化最小。
功能 updateProps(元件, componentProps) { 常量 道具 = propsStore。得到(元件) 宾语。项(道具)。的forEach(((键, 值)) => { 如果 (类型 值 === '功能') { 如果 (键。切片(0, 2) === '上') { 如果 (元件(键) == 值) { 元件(键) = 值 } 返回 } //没有已知的组件道具,没有游戏 如果 (componentProps) 返回 值 = 值。呼叫(元件, componentProps) } 其他 如果 (componentProps) { //这是一项减少工作的优化 //但是:也许以后会引入bug 返回 } 如果 (元件(键) == 值) { 元件(键) = 值 } }) }
在大多数情况下,我们只是通过我们感兴趣的道具。
新方法
我们有两种新方法,这两种方法都是:
功能 setParentComponent(元件, 分段, componentProps) { //已标记给其他人? 如果 (parentComponents。具有(元件)) { //检查此元素的父组件是否具有父组件 常量 亲 = parentComponents。得到(元件) 如果 (parentComponents。具有(亲)) parentComponents。组(亲, 分段) 返回 } //我们正在追踪这个元素吗? 如果 (propsStore。具有(元件)) 返回 //标记父级并管理道具,然后继续使用子级 parentComponents。组(元件, 分段) updateProps(元件, componentProps) 对于 (让 儿童 的 元件。的childNodes) { setParentComponent(儿童, 分段, componentProps) } } 功能 updateComponentProps(componentProps, 道具, parentProps = {}) { 返回 宾语。项(道具)。降低((componentProps, (键, 值)) => { 如果 (类型 值 === '功能' && 键。切片(0, 2) == '上') { componentProps(键) = 值(parentProps) } 返回 componentProps }, componentProps) }
这就是拼图的最后一部分。已取得的成果摘要:
- 组件呈现为片段
- 组件现在知道他们的每个孩子,包括其他组件
- 我们可以将组件的道具传递给它们的子函数
- 组件可以随着道具的变化而更新
该库现在已经获得了很多功能,但总代码不到100行我们来看看一个有效的应用程序:
是时候反思一下了。我知道这篇文章系列并没有以一种方便的步骤方式进行教学:我没有太多关注细节,而是使用工作代码进行工作。但是,我希望到目前为止的内容已经让我们深入了解了有经验的开发人员如何处理事情以及如何将一个想法构建到一个完全可用的库中。随意在评论中提出问题,反馈和批评
在下一部分中,是时候管理当前应用程序端代码中的最后一个烦恼:摆脱 给予
和 REF
其他部分:1,2