{"id":13817783,"url":"https://github.com/chinanf-boy/ink-explain","last_synced_at":"2025-12-31T14:35:10.708Z","repository":{"id":90548417,"uuid":"127721727","full_name":"chinanf-boy/ink-explain","owner":"chinanf-boy","description":"explain : 「 ink 」对CLI做出-React。 使用组件构建和测试您的CLI输出","archived":false,"fork":false,"pushed_at":"2018-04-02T08:38:11.000Z","size":10,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-02T18:15:58.606Z","etag":null,"topics":["cli","explain","ink","like-react","terminal"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chinanf-boy.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-04-02T07:36:23.000Z","updated_at":"2025-03-31T13:23:24.000Z","dependencies_parsed_at":"2023-07-18T11:00:08.877Z","dependency_job_id":null,"html_url":"https://github.com/chinanf-boy/ink-explain","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chinanf-boy/ink-explain","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fink-explain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fink-explain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fink-explain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fink-explain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chinanf-boy","download_url":"https://codeload.github.com/chinanf-boy/ink-explain/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chinanf-boy%2Fink-explain/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265829015,"owners_count":23835083,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","explain","ink","like-react","terminal"],"created_at":"2024-08-04T06:00:55.458Z","updated_at":"2025-12-31T14:35:10.681Z","avatar_url":"https://github.com/chinanf-boy.png","language":null,"funding_links":[],"categories":["Others"],"sub_categories":[],"readme":"# ink\n\n「 对CLI做出-React。 使用组件构建和测试您的CLI输出. 」\n\n[![explain](http://llever.com/explain.svg)](https://github.com/chinanf-boy/Source-Explain)\n    \nExplanation\n\n\u003e \"version\": \"0.4.1\"\n\n[github source](https://github.com/vadimdemedes/ink)\n\n~~[english](./README.en.md)~~\n\n---\n\n### 本次例子\n\n-[官方](https://github.com/vadimdemedes/ink#usage)\n\n\u003cdetails\u003e\n\n```jsx\nconst {h, render, Component, Text} = require('ink');\n\nclass Counter extends Component {\n\tconstructor() {\n\t\tsuper();\n\n\t\tthis.state = {\n\t\t\ti: 0\n\t\t};\n\t}\n\n\trender() {\n\t\treturn h('div', {}, [\n\t\t\th('div', {}, []),\n\t\t\th('div', {}, [\n\t\t\t\th(Text, {blue: true}, '~/Projects/ink ')\n\t\t\t]),\n\t\t\th('div', {}, [\n\t\t\t\th(Text, {red: true}, 'λ '),\n\t\t\t\th(Text, {green: true}, 'node '),\n\t\t\t\th(Text, {}, 'media/example')\n\t\t\t]),\n\t\t\th(Text, {green: true}, `${this.state.i} tests passed`)\n\t\t]);\n\t}\n\n\tcomponentDidMount() {\n\t\tconsole.log()\n\t\tthis.timer = setInterval(() =\u003e {\n\t\t\tif (this.state.i === 50) {\n\t\t\t\tprocess.exit(0); // eslint-disable-line unicorn/no-process-exit\n\t\t\t}\n\n\t\t\tthis.setState({\n\t\t\t\ti: this.state.i + 1\n\t\t\t});\n\t\t}, 100);\n\t}\n\n\tcomponentWillUnmount() {\n\t\tclearInterval(this.timer);\n\t}\n}\n\nrender(h(Counter));\n\n```\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"media/demo.svg\" width=\"600\"\u003e\n\u003c/p\u003e\n\n\u003c/details\u003e\n\n\n---\n\n本目录\n\n---\n\n可以看到例子运行, `render(h(Counter));` 首先触发 `h`\n\n---\n\n## 0. h\n\n`ink/lib/h.js`\n\n\u003e 将-`Component`/`\"div/span/br\"`-变成-`Vnode`\n\n\u003cdetails\u003e\n\n``` js\n'use strict';\n\nconst flatten = require('lodash.flattendeep');\nconst VNode = require('./vnode');\n\n// class 其实也是 函数的一种\nmodule.exports = (component, props, ...children) =\u003e {\n\tif (typeof component !== 'function' \u0026\u0026 typeof component !== 'string') {\n\t\tthrow new TypeError(`Expected component to be a function, but received ${typeof component}. You may have forgotten to export a component.`);\n\t}\n\n\tprops = props || {};\n\n\tconst readyChildren = [];\n\n\t// 孩子 \n\tif (children.length \u003e 0) {\n\t\tprops.children = children;\n\t}\n\n\t// 对那些 h(Component) / 其他类型 孩子改造\n\tflatten(props.children).forEach(child =\u003e {\n\t\tif (typeof child === 'number') {\n\t\t\t// 数字 孩子\n\t\t\tchild = String(child);\n\t\t}\n\n\t\tif (typeof child === 'boolean' || child === null) {\n\t\t\t// 异类孩子\n\t\t\tchild = '';\n\t\t}\n\n\t\tif (typeof child === 'string') {\n\t\t\t// 字符串孩子\n\t\t\tif (typeof readyChildren[readyChildren.length - 1] === 'string') {\n\t\t\t\t// 最后孩子是否同样是 字符串\n\t\t\t\t// 合并\n\t\t\t\treadyChildren[readyChildren.length - 1] += child;\n\t\t\t} else {\n\t\t\t\treadyChildren.push(child);\n\t\t\t}\n\t\t} else {\n\t\t\t// 剩下的就是 h(Component) -\u003e new VNode 孩子\n\t\t\treadyChildren.push(child);\n\t\t}\n\t\t// 不管怎么样都放进 readyChildren\n\t});\n\n\tprops.children = readyChildren;\n// 把一系列的 选项打入 props 直接交给 VNode\n\treturn new VNode(component, props);\n};\n\n```\n\n- VNode\n\n\u003e 1. 有两种组件 div/span/br string 类型 | 通过 extend Component 类型\n\n\u003e 2. VNode类可以说扩展-Component类, 也就是只是其中一个 VNode.component = component 属性\n\n``` js\nconst getComponent = name =\u003e {\n\tswitch (name) {\n\t\tcase 'div': return Div; // 相当于 孩子 + 换行\n\t\tcase 'span': return Span; // 相当于 孩子\n\t\tcase 'br': return Br; // // 相当于 换行\n\t\tdefault: return null;\n\t}\n};\n\nclass VNode {\n\tconstructor(component, props = {}) {\n\t\t\n\t\tconst ref = props.ref;\n\t\tdelete props.ref;\n\n\t\t// 组件类\n\t\tthis.component = typeof component === 'string' ? getComponent(component) : component;\n\t\t// 分析选项\n\t\tthis._props = transformProps(props);\n\t\tthis._children = [];\n\t\t// 指定的组件\n\t\tthis.ref = ref;\n\t\tthis.instance = null;\n\t}\n\n\t// 定义 - 属性规则\n\tget props() {\n\t\treturn this._props;\n\t}\n\n\tset props(nextProps) {\n\t\tthis._props = transformProps(nextProps);\n\t\treturn this._props;\n\t}\n\n\tget children() {\n\t\treturn this._children;\n\t}\n\n\tset children(nextChildren) {\n\t\tthis._children = flatten(arrify(nextChildren));\n\t\treturn this._children;\n\t}\n\n\t// 在一定时候, 需要将  Component 类 实例化\n\tcreateInstance(props) {\n\t\t// 只会将 继承 Component 的类实例化\n\t\tif (isClassComponent(this.component)) {\n\t\t\tthis.instance = new this.component(props, {});\n\t\t}\n\t}\n}\n\nif (process.env.NODE_ENV !== 'production') {\n\t// 确定一个 不可变类型\n\tVNode.prototype.$$typeof = Symbol.for('react.element');\n}\n\nmodule.exports = VNode;\n\n```\n\n---\n\n通过 `h` 和 `VNode` 的 洗刷\n\n例子🌰中的 `Counter` -\u003e `VNode`\n\n要⚠️注意的是 `Coumter.render` 里面的 渲染, 并没有触发。\n\nVNode(Counter) 只是一个很原型的 虚拟节点「VNode」, 没有孩子\n\n👇下一步, 我们把 \b统一好的`VNode` -\u003e [`render(VNode)`](#1-render)\n\n\u003e 但是在 `render` 中, 主要说得是, 接管终端 输出/ 输入\n\n\u003c/details\u003e\n\n\n---\n\n## 1. render\n\n`ink/index.js`\n\n代码 30-119\n\n\u003e 接管终端 输出/ 输入, 和重覆盖\n\n\u003cdetails\u003e\n\n``` js\nexports.render = (tree, options) =\u003e {\n    // tree == h(Counter)\n\tif (options \u0026\u0026 typeof options.write === 'function') {\n\t\toptions = {\n\t\t\tstdout: options\n\t\t};\n\t}\n\n\tconst {stdin, stdout} = Object.assign({\n\t\tstdin: process.stdin, // cli-输入\n\t\tstdout: process.stdout // cli-输出\n\t}, options);\n\n\tconst log = logUpdate.create(stdout); // log-update 是 对 通过覆盖终端中的前一个输出进行记录。\n\n\tconst context = {};\n\tlet isUnmounted = false;\n\tlet currentTree;\n\n    readline.emitKeypressEvents(stdin);\n    // 相应于接收到的输入触发 'keypress' 事件。\n\n\tif (stdin.isTTY) {\n        stdin.setRawMode(true);\n//把 tty.ReadStream 配置成原始模式。\n// 在原始模式中，输入按字符逐个生效，但不包括修饰符。\n\t}\n\n\tconst update = () =\u003e {\n\t\tconst nextTree = build(tree, currentTree, onUpdate, context); // 确定✅-下一个树\n\t\tlog(renderToString(nextTree)); // 覆盖\n\n\t\tcurrentTree = nextTree;\n\t}; // 更新终端视图\n\n\tconst onUpdate = () =\u003e { // 给予 diff 更新函数\n\t\tif (isUnmounted) {\n\t\t\treturn;\n\t\t}\n\n\t\tupdate();\n\t};\n\n\tupdate(); // 先运行一边\n\n\tconst onKeyPress = (ch, key) =\u003e {\n\t\tif (key.name === 'escape' || (key.ctrl \u0026\u0026 key.name === 'c')) {\n\t\t\texit();// 退出\n\t\t}\n\t}; // 终端输入键-监控-触发事件\n\n\tif (stdin.isTTY) {\n\t\tstdin.on('keypress', onKeyPress); // 监控输入键\n\t\tstdout.on('resize', update); // 监控 终端重载\n\t}\n\n\tconst consoleMethods = ['dir', 'log', 'info', 'warn', 'error'];\n\n\tconsoleMethods.forEach(method =\u003e {\n\t\tconst originalFn = console[method];\n\n\t\tconsole[method] = (...args) =\u003e {\n\t\t\tlog.clear();\n\t\t\tlog.done();\n\t\t\toriginalFn.apply(console, args);\n\t\t\tupdate();\n\t\t};\n\n\t\tconsole[method].restore = () =\u003e {\n\t\t\tconsole[method] = originalFn;\n\t\t};\n\t}); // 控制 console.** 函数 对终端视图的输出\n\n\tconst exit = () =\u003e {\n\t\tif (isUnmounted) { // 已经拆了\n\t\t\treturn;\n\t\t}\n\n\t\tif (stdin.isTTY) {\n\t\t\tstdin.setRawMode(false); // 默认模式\n\t\t\tstdin.removeListener('keypress', onKeyPress); // 移除\n\t\t\tstdin.pause(); // 输入暂停\n\t\t\tstdout.removeListener('resize', update); // 移除\n\t\t}\n\n\t\tisUnmounted = true; // 拆了\n        build(null, currentTree, onUpdate, context);\n        // 终端视图-归零\n\t\tlog.done(); // 最重要的是这个, 放开终端输出\n\n\t\tconsoleMethods.forEach(method =\u003e console[method].restore()); // 把原来的 换回去\n\t};\n\n\treturn exit;\n};\n\n```\n\n- [readline.emitKeypressEvents](http://nodejs.cn/api/readline.html#readline_readline_emitkeypressevents_stream_interface)\n\n\u003e 相应于接收到的输入触发 'keypress' 事件。重置所有事件, 然后自定义事件\n\n- [stdin.setRawMode(true)](http://nodejs.cn/api/tty.html#tty_readstream_setrawmode_mode)\n\n\u003e 配置成原始模式。在原始模式中，输入按字符逐个生效，但不包括修饰符。\n\n- [require('log-update')](https://github.com/sindresorhus/log-update)\n\n\u003e 说白了, 所有的类React, 组件定义等等, 最后都输出到 `log-update` 这个控制终端输出的库中, 通过覆盖终端中的前一个输出进行记录。用于渲染进度条，动画等! `log-update by sindresorhus`\n\n- [build](#2-build)\n\n\u003e 用来建立新的-视图树`Tree`, 也就`❤️最重要❤️`的\n\n- [renderToString](#4-rendertostring)\n\n\u003e 把 视图树`VNode 类` 变成 `String`， 给 `log-update` 使用\n\n\u003e `log(renderToString(nextTree));`\n\n\u003c/details\u003e\n\n---\n\n## 2. build\n\n`ink/lib/index.js`\n\n\u003e 用来建立新的-视图树`Tree`\n\n代码 19-23\n\n``` js\nconst noop = () =\u003e {};\n\nconst build = (nextTree, prevTree, onUpdate = noop, context = {}) =\u003e {\n    // 初始化 prevTree undefaulted\n\treturn diff(prevTree, nextTree, onUpdate, context);\n};\n```\n\n那么到了这里-`diff`-「有关 比较新旧树 触发生命周期钩子, 改变-虚拟树」\n\n\u003e 但在这例子中, 仅仅是自己与自己的状态比较, [`有关setState 的触发机制`](#5-setstate)\n\n---\n\n---\n\n## 3. diff\n\n`ink/lib/diff.js`\n\n我们前面已经说明了, 在如何接管-终端输出和输入, 已达到覆盖的动画等效果\n\n\u003e 对比-视图树, 返回新或者没有变的树🌲\n\n\u003cdetails\u003e\n\n\u003e⚠️ onUpdate 也跟进来了, 这是有关[setState 触发重覆盖的问题](#5-setstate)\n\n``` js\nconst diff = (prevNode, nextNode, onUpdate, context) =\u003e {\n// 初始化时, prevNode 未声明\n\tif (typeof nextNode === 'number') { // 如果是 数字\n\t\tif (prevNode instanceof VNode) {\n\t\t\tunmount(prevNode);\n\t\t}\n\n\t\treturn String(nextNode); // 转 字符串\n\t}\n\n\tif (!nextNode || typeof nextNode === 'boolean') { \n        // 如果是 正负\n\t\tif (prevNode instanceof VNode) {\n\t\t\tunmount(prevNode);\n\t\t}\n\n\t\treturn null; // 返回 🈳️\n\t}\n\n\tif (typeof nextNode === 'string') { // 如果 字符串\n\t\tif (prevNode instanceof VNode) {\n\t\t\tunmount(prevNode);\n\t\t}\n\n\t\treturn nextNode; // 返回 下个🌲\n\t}\n\n    let isPrev = true; // 如果true , 不需要更新 \n    // 其实改为 isPrev 可能好理解点, just do it\n\n\tif (!(prevNode instanceof VNode)) { // 如果上一个不是VNode\n\t\tmount(nextNode, context, onUpdate);\n\t\tisPrev = false; // 如果false, 更新-新的\n\t}\n\n\tif (isPrev \u0026\u0026 prevNode.component !== nextNode.component) { // 直接对比 Component 类\n        unmount(prevNode); \n        // 移除旧, 触发 componentWillUnmount\n        mount(nextNode, context, onUpdate); \n        // 加载新的 注意⚠️ onUpdate 是覆盖-终端输出的关键\n        // 触发 componentWillMount\n\t\tisPrev = false;\n    } \n    \n// 如果到这里 isPrev 还是 == true, 说明自己和自己,判断还没有结束\n//⏰ 下面这部分, 就开始细分更新, 和 剩下组件事件钩子的运行\n\n    // 对比 Component props\n    const shouldUpdate = isPrev \u0026\u0026 shouldComponentUpdate(prevNode, getProps(nextNode), getNextState(prevNode));\n\n// 为什么是 prevNode, 因为还是 isPrev , 这里就自己和自己比较的开始了\n\n    // 旧树的孩子\n\tconst prevChildren = isPrev ? [].slice.call(prevNode.children) : [];\n\n\tif (isPrev \u0026\u0026 !isEqualShallow(getProps(prevNode), getProps(nextNode))) {\n\t\tcomponentWillReceiveProps(prevNode, getProps(nextNode));\n\t}\n\n\tif (shouldUpdate) { // 应该重新渲染\n\t\trerender(prevNode, context); // ???\n\t}\n\n    // 下一个树孩子\n\tconst nextChildren = isPrev ? prevNode.children : nextNode.children;\n\n\tconst length = Math.max(prevChildren.length, nextChildren.length);\n\tconst reconciledChildren = [];\n\n\tfor (let index = 0; index \u003c length; index++) { \n        // 递归把 孩子 整回来\n        const childNode = diff(prevChildren[index], nextChildren[index], onUpdate, context);\n        \n\t\treconciledChildren.push(childNode);\n\t}\n\n\tif (isPrev) {\n        // 旧树\n\t\tprevNode.children = reconciledChildren;\n\n\t\tif (shouldUpdate) {\n            // 触发更新-旧树\n\t\t\tcomponentDidUpdate(prevNode);\n\t\t}\n\t} else {\n        // 新树\n        nextNode.children = reconciledChildren;\n         // 触发更新\n\t\tcomponentDidMount(nextNode);\n    }\n    // 反正 componentDidMount 是一定要触发的\n\n\treturn isPrev ? prevNode : nextNode;\n};\n```\n\n#### 3.1 `mount`\n\n\u003e 1. 将 vnode.component 实例化 vnode.instance\n\n\u003e 2. 触发 载入前钩子-componentWillMount\n\n\u003e 3. _render()触发-\u003e生成孩子虚拟树\n\n``` js\nconst mount = (vnode, context, onUpdate) =\u003e {\n\tconst props = getProps(·);\n\tcheckPropTypes(vnode.component, props);\n\n\tif (isClassComponent(vnode.component)) {\n// 1. 继承Component的类\n\t\tvnode.createInstance(props);\n\t\tvnode.instance._onUpdate = onUpdate;\n\n\t\t// context 是 一个全局上下文\n\t\tvnode.instance.context = Object.assign(context, vnode.instance.getChildContext());\n\t\t// 2.\n\t\tvnode.instance.componentWillMount();\n\t\t// 3.\n\t\tvnode.children = vnode.instance._render();\n\t} else {\n// 关于 孩子 换行之类\n// div/span/br\n\t\tvnode.children = vnode.component(props, context);\n\t}\n};\n```\n\n#### 3.2 `unmount`\n\n\u003e 1. 触发 卸载前-componentWillUnmount\n\n\u003e 2. 清空 实例\n\n\u003e 3. 全部孩子去掉\n\n\u003e 4. 去掉指定组件\n\n``` js\nconst unmount = vnode =\u003e {\n\tif (isClassComponent(vnode.component)) {\n\t\tcomponentWillUnmount(vnode);\n\t\tvnode.instance = null;\n\t}\n\n\tvnode.children.forEach(childVNode =\u003e {\n\t\tdiff(childVNode, null);\n\t});\n\n// 去掉全局变量\n\tif (isClassComponent(vnode.component) \u0026\u0026 vnode.ref) {\n\t\tvnode.ref(null);\n\t}\n};\n```\n\n---\n\n🧠 在`diff函数`我们需要什么? `return isPrev ? prevNode : nextNode;`\n\n那么 我们需要一个改造好的 虚拟树。\n\n这么一堆的判断, 生命周期钩子触发, Instance化, 但我们返回的也就是-VNode类, \n\n只是 `VNode.instance` 不再是 `null`, \n\n`VNode.children` 也 `_render()触发` 回来 变成 `VNode`树\n\n\n\u003c/details\u003e\n\n\n---\n\n## 4. renderToString\n\n\u003e 把 `Vnode树` 变成 `String`\n\n\u003cdetails\u003e\n\n`/ink/lib/render-to-string.js`\n\n``` js\n'use strict';\n\nconst StringComponent = require('./string-component');\n\nconst renderToString = vnode =\u003e {\n\tif (!vnode) {\n// 1.\n\t\treturn '';\n\t}\n\n\tif (typeof vnode === 'string') {\n// 2.\n\t\treturn vnode; // children 返回\n\t}\n\n\tif (Array.isArray(vnode)) { // 数组VNode, h(Component, props, children)\n\t// children 都是数组, 所有通过这样 join('') 组合\n\t\treturn vnode\n\t\t\t.map(renderToString)\n\t\t\t.join('');\n\t}\n\n\tif (vnode.instance instanceof StringComponent) {\n\n// 真正停止的递归终止条件, \n// 1. ''\n// 2. String\n// 3. 是 继承 StringComponent Text\n\n// 也就是 Text, 作者定义的字符串组件\n// 可以看到本项目例子中 Text, \n\n\t\t// 我们上面 3.1 mount 就说了 vnode.children 是 vnode.instance._render() 赋予的\n\t\t// 4.1 StringComponent children 如何是字符串\n\t\tconst children = renderToString(vnode.children);\n\t\t// 4.2  染色字符串\n\t\treturn vnode.instance.renderString(children); \n\t}\n\n\t// 递归-孩子\n\treturn renderToString(vnode.children);\n};\n\nmodule.exports = renderToString;\n\n```\n\n\n#### 4.1 `StringComponent children 如何是字符串`\n\n`ink/lib/string-component.js`\n\n``` js\nclass StringComponent extends Component {\n\trender() {\n\t\treturn this.props.children;\n\t}\n}\n```\n\n\u003e 可以看到[本项目例子中 Text](#本次例子)\n\n\u003e h(Text, {blue: true}, '~/Projects/ink ') =\u003e\n\n\u003e '~/Projects/ink ' == props.children =\u003e\n\n\u003e vnode.instance._render() 触发 StringComponent.render() =\u003e\n\n\u003e vnode.children = this.props.children == '~/Projects/ink '\n\n---\n\n#### 4.2 `染色字符串`\n\n`ink/lib/components/text.js`\n\n代码 22-36\n\n``` js\n// 没错, 为什么可以设置颜色, 就是 chalk 输出颜色库\n// h(Text, {blue: true}, '~/Projects/ink ')\n\nclass Text extends StringComponent {\n\trenderString(children) {\n\t\tObject.keys(this.props).forEach(method =\u003e {\n\t\t\tif (this.props[method]) {\n\t\t\t\tif (methods.includes(method)) {\n\t\t\t\t\tchildren = chalk[method].apply(chalk, arrify(this.props[method]))(children);\n\t\t\t\t} else if (typeof chalk[method] === 'function') {\n\t\t\t\t\tchildren = chalk[method](children);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn children; // 染好的字符串\n\t}\n}\n```\n\n\u003c/details\u003e\n\n\n---\n\n## 5. setState\n\n\u003e Component 组件中改变 状态的函数, 却也是触发, 重覆盖的函数\n\n怎么做到, 点击⬇️\n\n\u003cdetails\u003e\n\n让我们回到 Component 类的定义\n\n`ink/lib/component.js`\n\n代码 15-27\n\n``` js\n\tsetState(nextState, callback) {\n\t\tif (typeof nextState === 'function') {\n\t\t\tnextState = nextState(this.state, this.props);\n\t\t}\n\n\t\tthis._pendingState = Object.assign({}, this._pendingState || this.state, nextState);\n\n\t\tif (typeof callback === 'function') {\n\t\t\tthis._stateUpdateCallbacks.push(callback);\n\t\t}\n\n\t\tthis._enqueueUpdate(); // 《=== 触发\n\t}\n```\n\n代码 70-72\n\n``` js\n\t_enqueueUpdate() {\n\t\tenqueueUpdate(this._onUpdate); \n\t\t// 把 组件的 _onUpdate 放进去\n\n//🤔 _onUpdate 怎么来的, 其实本质就是\n// `ink/index.js` 61-67\n\n\t// const onUpdate = () =\u003e {\n\t// \tif (isUnmounted) {\n\t// \t\treturn;\n\t// \t}\n\n\t// \tupdate();\n\t// };\n\n// 在做比较-diff-函数时就一直在变量中流传\n// diff(prevTree, nextTree, onUpdate, context);\n\t}\n\n```\n\n代码 3\n\n``` js\nconst {enqueueUpdate} = require('./render-queue');\n\n```\n\n`ink/lib/render-queue.js`\n\n``` js\n'use strict';\n\nconst options = require('./options');\n\nconst queue = [];\n\nconst rerender = () =\u003e {\n\twhile (queue.length \u003e 0) {\n\t\tconst callback = queue.pop();\n\t\tcallback();\n\t}\n};\n\nexports.rerender = rerender;\n\nexports.enqueueUpdate = callback =\u003e {\n\tqueue.push(callback);\n\toptions.deferRendering(rerender); \n\t// \u003c=== 函数在下一次事件轮询调用\n\t// 也就是 onUpdate 的调用\n};\n\n```\n\n`ink/lib/options.js`\n\n``` js\n'use strict';\n\nmodule.exports = {\n\tdeferRendering: process.nextTick //\u003c===\n};\n\n```\n\n- [`process.nextTick`](http://nodejs.cn/api/process.html#process_process_nexttick_callback_args)\n\n\u003e 一旦当前事件轮询队列的任务全部完成，在next tick队列中的所有callbacks会被依次调用。\n\n\u003c/details\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fink-explain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchinanf-boy%2Fink-explain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchinanf-boy%2Fink-explain/lists"}