In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-03-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly explains "how the vue compiler generates rendering functions". The explanation in this article is simple and clear and easy to learn and understand. Please follow the editor's train of thought to study and learn "how the vue compiler generates rendering functions".
Deep into the source code
CreateCompiler () method-entry
File location: / src/compiler/index.js
The most important one is the generate (ast, options) method, which is responsible for generating rendering functions from the AST syntax tree.
/ * everything I did before that was just to build platform-specific compilation options (options) For example, web platform 1, parsing html templates into ast 2, statically tagging ast trees 3, putting ast generation rendering functions into code.staticRenderFns arrays-dynamic rendering functions code.render-performing rendering functions in the future can get vnode * / export const createCompiler = createCompilerCreator (function baseCompile (template: string) Options: CompilerOptions): CompiledResult {/ * parses the template string to the AST syntax tree. All the information of the element is set on the ast object of each node of the syntax tree. For example, tag information, attribute information, slot information, parent node, child node, etc. * / const ast = parse (template.trim (), options) / * optimize, traverse the AST, make a static tag for each node-mark whether each node is a static node, ensure that these static nodes are skipped in subsequent updates-mark the static root node, which is used to generate the rendering function phase Render function optimization for generating static root nodes, traversing AST Make a static tag for each node * / if (options.optimize! = = false) {optimize (ast, options)} / * generate rendering functions from the AST syntax tree such as: code.render = "_ c ('div', {attrs: {" id ":" app "}}, _ l ((arr), function (item) {return _ c (' div', {key:item}, [_ v (item)])})) 0) "* / const code = generate (ast, options) return {ast, render: code.render, staticRenderFns: code.staticRenderFns}})
Generate () method
File location: src\ compiler\ codegen\ index.js
When assigning values to code, the main content is generated by genElement (ast, state) method.
/ * generate rendering function from AST:-render is a string code-staticRenderFns is a code containing multiple strings in the form of `with (this) {return xxx} `* / export function generate (ast: ASTElement | void, / / ast object options: CompilerOptions / / compilation option): CodegenResult {/ * instantiates the CodegenState object, and the parameter is the compilation option, resulting in state Most of these attributes are the same as options * / const state = new CodegenState (options) / * generate string format code, for example:'_ c (tag, data, children, normalizationType)'- data forms JSON strings for attributes on the node, such as'{key: xx, ref: xx,...}'- children string array of all child nodes' string format code Format: `['_ c (tag, data, children)',...], normalizationType`,-normalization is the fourth parameter of _ c, indicating the normalization type of the node (not important, can be skipped) Note: code is not necessarily _ c, it may also be other, for example, if the whole component is static, the result is _ m (0) * / const code = ast? (ast.tag = 'script'? 'null': genElement (ast, state):' _ c ("div") 'return {render: `with (this) {return ${code}} `, staticRenderFns: state.staticRenderFns}}
GenElement () method
File location: src\ compiler\ codegen\ index.js
Export function genElement (el: ASTElement, state: CodegenState): string {if (el.parent) {el.pre = el.pre | | el.parent.pre} if (el.staticRoot & &! el.staticProcessed) {/ * dealing with static root nodes Generate the render function of the node 1, put the render function of the current static node into the staticRenderFns array 2, return an executable function _ m (idx, true or') * / return genStatic (el, state)} else if (el.once & &! el.onceProcessed) {/ * process the node with the v-once instruction There will be three kinds of results: 1. There is a v-if instruction in the current node, and you get a ternary expression, `condition? Render1: render2`2. The current node is a static node included in the v-for instruction, so you get `o (_ c (tag, data, children), number, key) `3. The current node is a simple v-once node. Get `m (idx, true of'') `* / return genOnce (el, state)} else if (el.for & &! el.forProcessed) {/ * to process the v-for instruction on the node Get: `l (exp, function (alias, iterator1, iterator2) {return _ c (tag, data, children)}) `* / return genFor (el, state)} else if (el.if & &! el.ifProcessed) {/ * processes nodes with v-if instructions, resulting in a ternary expression: `condition? Render1: render2` * / return genIf (el, state)} else if (el.tag = = 'template' & &! el.slotTarget & &! state.pre) {/ * when the current node is a template tag, not a slot and a node with v-pre instruction, go here to generate rendering functions for all child nodes and return an array Format: `[_ c (tag, data, children, normalizationType),...]` * / return genChildren (el, state) | | 'void 0'} else if (el.tag = =' slot') {/ * generate slot rendering function Get: `_ t (slotName, children, attrs, bind)` * / return genSlot (el, state)} else {/ * component or element handles dynamic components and common elements (custom components, native tags, platform retention tags, such as every html tag in the web platform) * / let code if (el.component) {/ * handles dynamic components Generate rendering functions for dynamic components Get `c (compName, data, children) `* / code = genComponent (el.component, el, state)} else {/ / deal with common elements (custom components, native tags) let data if (! el.plain | | (el.pre & & state.maybeComponent (el) {/ * non-ordinary elements or components with v-pre instructions go here Process all the attributes of the node and return a JSON string, such as:'{key: xx, ref: xx,.}'* / data = genData (el, state)} / * process the child nodes to get an array of codes in the string format of all the child nodes Format: `['_ c (tag, data, children)',...], where normalization represents the normalization type of the node (non-key, can be skipped) * / const children = el.inlineTemplate? Null: genChildren (el, state, true) / * get the code in the final string format Format: _ c (tag, data, children, normalizationType) * / code = `_ c ('${el.tag}'${data?`, ${data} `:'/ / data} ${children?`, ${children} `:'/ / children})`} / * if the transformCode method is provided The final code will be processed by this method of each module (module), but the framework does not provide this method, but even if it is processed, the final format is _ c (tag, data, children) module transforms * / for (let I = 0) I
< state.transforms.length; i++) { code = state.transforms[i](el, code) } // 返回 code return code }} genChildren() 方法 文件位置:src\compiler\codegen\index.js /* 生成所有子节点的渲染函数,返回一个数组,格式如: `[_c(tag, data, children, normalizationType), ...]` */export function genChildren ( el: ASTElement, state: CodegenState, checkSkip?: boolean, altGenElement?: Function, altGenNode?: Function): string | void { // 获取所有子节点 const children = el.children if (children.length) { // 第一个子节点 const el: any = children[0] // optimize single v-for if (children.length === 1 && el.for && el.tag !== 'template' && el.tag !== 'slot' ) { /* 优化处理: - 条件:只有一个子节点 && 子节点的上有 v-for 指令 && 子节点的标签不为 template 或者 slot - 方式:直接调用 genElement 生成该节点的渲染函数,不需要走下面的循环然后调用 genCode 最后得到渲染函数 */ const normalizationType = checkSkip ? state.maybeComponent(el) ? `,1` : `,0` : `` return `${(altGenElement || genElement)(el, state)}${normalizationType}` } // 获取节点规范化类型,返回一个 number: 0、1、2(非重点,可跳过) const normalizationType = checkSkip ? getNormalizationType(children, state.maybeComponent) : 0 // 是一个函数,负责生成代码的一个函数 const gen = altGenNode || genNode /* 返回一个数组,其中每个元素都是一个子节点的渲染函数 格式:['_c(tag, data, children, normalizationType)', ...] */ return `[${children.map(c =>Gen (c, state) .join (',')}] ${normalizationType? `, ${normalizationType}`:''} `}}
GenNode () method
File location: src\ compiler\ codegen\ index.js
Function genNode (node: ASTNode, state: CodegenState): string {/ / process normal element node if (node.type = 1) {return genElement (node, state)} else if (node.type = 3 & & node.isComment) {/ / process text comment node return genComment (node)} else {/ / process text node return genText (node)}}
GenComment () method
File location: src\ compiler\ codegen\ index.js
/ / get the returned value in the format of: `_ e (xxxx)` export function genComment (comment: ASTText): string {return `_ e (${JSON.stringify (comment.text)})`}
GenText () method
File location: src\ compiler\ codegen\ index.js
/ / get the returned value in the format: `_ v (xxxxx)` export function genText (text: ASTText | ASTExpression): string {return `_ v (${text.type = 2? Text.expression / / no need for () because already wrapped in _ s (): transformSpecialNewlines (JSON.stringify (text.text))}) `}
GenData () method
File location: src\ compiler\ codegen\ index.js
/ * process many attributes on the node, and finally generate JSON strings composed of these attributes, such as data = {key: xx, ref: xx,...} * / export function genData (el: ASTElement, state: CodegenState): string {/ / Node JSON string let data ='{'/ * first process instructions Because instructions may change these attributes to execute instruction compilation methods before generating other attributes, such as v-text, v-html, v-model of the web platform, and then add corresponding attributes to the el object, such as v-text:el.textContent = _ s (value, dir) v-html: El [XSS _ clean] = _ s (value, dir) when the instruction is still running, such as v-model Return directives: [{name, rawName, value, arg, modifiers},...}] * / const dirs = genDirectives (el, state) if (dirs) data + = dirs +','/ key,data = {key: xxx} if (el.key) {data + = `key:$ {el.key}, `} / / ref Data = {ref: xxx} if (el.ref) {data + = `ref:$ {el.ref}, `} / / Node with ref attribute is inside the node with v-for directive, data = {refInFor:true} if (el.refInFor) {data + = `refInFor:true,`} / / pre,v-pre instruction Data = {pre:true} if (el.pre) {data + = `pre:true, `} / / dynamic component, data = {tag: 'component'} if (el.component) {data + = `tag: "${el.tag}",`} / * is the genData method of node execution module (class, style) Get data = {staticClass: xx, class: xx, staticStyle: xx, style: xx} module data generation functions * / for (let I = 0 I
< state.dataGenFns.length; i++) { data += state.dataGenFns[i](el) } /* 其它属性,得到 data = { attrs: 静态属性字符串 } 或者 data = { attrs: '_d(静态属性字符串, 动态属性字符串)' } attributes */ if (el.attrs) { data += `attrs:${genProps(el.attrs)},` } // DOM props,结果 el.attrs 相同 if (el.props) { data += `domProps:${genProps(el.props)},` } /* 自定义事件 - data = { `on${eventName}:handleCode` } 或者 - { `on_d(${eventName}:handleCode`, `${eventName},handleCode`) } event handlers */ if (el.events) { data += `${genHandlers(el.events, false)},` } /* 带 .native 修饰符的事件, - data = { `nativeOn${eventName}:handleCode` } 或者 - { `nativeOn_d(${eventName}:handleCode`, `${eventName},handleCode`) */ if (el.nativeEvents) { data += `${genHandlers(el.nativeEvents, true)},` } /* 非作用域插槽,得到 data = { slot: slotName } slot target only for non-scoped slots */ if (el.slotTarget && !el.slotScope) { data += `slot:${el.slotTarget},` } // scoped slots,作用域插槽,data = { scopedSlots: '_u(xxx)' } if (el.scopedSlots) { data += `${genScopedSlots(el, el.scopedSlots, state)},` } /* 处理 v-model 属性,得到 data = { model: { value, callback, expression } } component v-model */ if (el.model) { data += `model:{value:${el.model.value },callback:${el.model.callback },expression:${el.model.expression }},` } /* inline-template,处理内联模版,得到: data = { inlineTemplate: { render: function() { render 函数 }, staticRenderFns: [ function() {}, ... ] } } */ if (el.inlineTemplate) { const inlineTemplate = genInlineTemplate(el, state) if (inlineTemplate) { data += `${inlineTemplate},` } } // 删掉 JSON 字符串最后的 逗号,然后加上闭合括号 } data = data.replace(/,$/, '') + '}' /* v-bind 动态参数包装 必须使用相同的 v-bind 对象应用动态绑定参数 合并辅助对象,以便正确处理 class/style/mustUseProp 属性。 */ if (el.dynamicAttrs) { data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})` } // v-bind data wrap if (el.wrapData) { data = el.wrapData(data) } // v-on data wrap if (el.wrapListeners) { data = el.wrapListeners(data) } return data} genDirectives() 方法 文件位置:src\compiler\codegen\index.js /** 运行指令的编译方法,如果指令存在运行时任务,则返回 directives: [{ name, rawName, value, arg, modifiers }, ...}] */function genDirectives(el: ASTElement, state: CodegenState): string | void { // 获取指令数组 const dirs = el.directives // 不存在指令,直接结束 if (!dirs) return // 指令的处理结果 let res = 'directives:[' // 用于标记指令是否需要在运行时完成的任务,比如 v-model 的 input 事件 let hasRuntime = false let i, l, dir, needRuntime // 遍历指令数组 for (i = 0, l = dirs.length; i < l; i++) { dir = dirs[i] needRuntime = true // 获取节点当前指令的处理方法,比如 web 平台的 v-html、v-text、v-model const gen: DirectiveFunction = state.directives[dir.name] if (gen) { // 执行指令的编译方法,如果指令还需要运行时完成一部分任务,则返回 true,比如 v-model needRuntime = !!gen(el, dir, state.warn) } if (needRuntime) { // 表示该指令在运行时还有任务 hasRuntime = true // res = directives:[{ name, rawName, value, arg, modifiers }, ...] res += `{name:"${dir.name}",rawName:"${dir.rawName}"${dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : '' }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : '' }${dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : '' }},` } } // 只有指令存在运行时任务时,才会返回 res if (hasRuntime) { return res.slice(0, -1) + ']' }} genDirectives() 方法 文件位置:src\compiler\codegen\index.js /* 遍历属性数组 props,得到所有属性组成的字符串 如果不存在动态属性,则返回:'attrName,attrVal,...' 如果存在动态属性,则返回:'_d(静态属性字符串, 动态属性字符串)' */function genProps(props: Array): string { // 静态属性 let staticProps = `` // 动态属性 let dynamicProps = `` // 遍历属性数组 for (let i = 0; i < props.length; i++) { // 属性 const prop = props[i] // 属性值 const value = __WEEX__ ? generateValue(prop.value) : transformSpecialNewlines(prop.value) if (prop.dynamic) { // 动态属性,`dAttrName,dAttrVal,...` dynamicProps += `${prop.name},${value},` } else { // 静态属性,'attrName:attrVal,...' staticProps += `"${prop.name}":${value},` } } // 闭合静态属性字符串,并去掉静态属性最后的 ',' staticProps = `{${staticProps.slice(0, -1)}}` if (dynamicProps) { // 如果存在动态属性则返回:_d(静态属性字符串,动态属性字符串) return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])` } else { // 说明属性数组中不存在动态属性,直接返回静态属性字符串 return staticProps }} genHandlers() 方法 文件位置:src\compiler\codegen\events.js /* 生成自定义事件的代码 动态:'nativeOn|on_d(staticHandlers, [dynamicHandlers])' 静态:`nativeOn|on${staticHandlers}` */export function genHandlers ( events: ASTElementHandlers, isNative: boolean): string { // 原生为 nativeOn,否则为 on const prefix = isNative ? 'nativeOn:' : 'on:' // 静态 let staticHandlers = `` // 动态 let dynamicHandlers = `` /* 遍历 events 数组 events = [{ name: { value: 回调函数名, ... } }] */ for (const name in events) { const handlerCode = genHandler(events[name]) if (events[name] && events[name].dynamic) { // 动态,dynamicHandles = `eventName,handleCode,...,` dynamicHandlers += `${name},${handlerCode},` } else { // staticHandlers = `eventName:handleCode,...,` staticHandlers += `"${name}":${handlerCode},` } } // 闭合静态事件处理代码字符串,去除末尾的 ',' staticHandlers = `{${staticHandlers.slice(0, -1)}}` if (dynamicHandlers) { // 动态,on_d(statickHandles, [dynamicHandlers]) return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])` } else { // 静态,`on${staticHandlers}` return prefix + staticHandlers }} genStatic() 方法 文件位置:src\compiler\codegen\index.js /* 生成静态节点的渲染函数 1、将当前静态节点的渲染函数放到 staticRenderFns 数组中 2、返回一个可执行函数 _m(idx, true or '') hoist static sub-trees out*/function genStatic(el: ASTElement, state: CodegenState): string { // 标记当前静态节点已经被处理过了 el.staticProcessed = true /* 某些元素(模板)在 v-pre 节点中需要有不同的行为 所有 pre 节点都是静态根,因此可将其用作包装状态更改并在退出 pre 节点时将其重置 */ const originalPreState = state.pre if (el.pre) { state.pre = el.pre } /* 将静态根节点的渲染函数 push 到 staticRenderFns 数组中, 比如:[`with(this){return _c(tag, data, children)}`] */ state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`) state.pre = originalPreState /* 返回一个可执行函数:_m(idx, true or '') idx = 当前静态节点的渲染函数在 staticRenderFns 数组中下标 */ return `_m(${state.staticRenderFns.length - 1 }${el.staticInFor ? ',true' : '' })`} genOnce() 方法 文件位置:src\compiler\codegen\index.js /* 处理带有 v-once 指令的节点,结果会有三种: 1、当前节点存在 v-if 指令,得到一个三元表达式,condition ? render1 : render2 2、当前节点是一个包含在 v-for 指令内部的静态节点, 得到 `_o(_c(tag, data, children), number, key)` 3、当前节点就是一个单纯的 v-once 节点,得到 `_m(idx, true of '')` v-once */function genOnce(el: ASTElement, state: CodegenState): string { // 标记当前节点的 v-once 指令已经被处理过了 el.onceProcessed = true if (el.if && !el.ifProcessed) { /* 如果含有 v-if 指令 && if 指令没有被处理过 则处理带有 v-if 指令的节点,最终得到一个三元表达式: condition ? render1 : render2 */ return genIf(el, state) } else if (el.staticInFor) { /* 说明当前节点是被包裹在还有 v-for 指令节点内部的静态节点 获取 v-for 指令的 key */ let key = '' let parent = el.parent while (parent) { if (parent.for) { key = parent.key break } parent = parent.parent } // key 不存在则给出提示,v-once 节点只能用于带有 key 的 v-for 节点内部 if (!key) { process.env.NODE_ENV !== 'production' && state.warn( `v-once can only be used inside v-for that is keyed. `, el.rawAttrsMap['v-once'] ) return genElement(el, state) } // 生成 `_o(_c(tag, data, children), number, key)` return `_o(${genElement(el, state)},${state.onceId++},${key})` } else { /* 上面几种情况都不符合,说明就是一个简单的静态节点, 和处理静态根节点时的操作一样,得到 _m(idx, true or '') */ return genStatic(el, state) }} genFor() 方法 文件位置:src\compiler\codegen\index.js /* 处理节点上 v-for 指令 得到 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})`*/export function genFor( el: any, state: CodegenState, altGen?: Function, altHelper?: string): string { // v-for 的迭代器,比如 一个数组 const exp = el.for // 迭代时的别名 const alias = el.alias // iterator 为 v-for = "(item ,idx) in obj" 时会有,比如 iterator1 = idx const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' // 提示,v-for 指令在组件上时必须使用 key if (process.env.NODE_ENV !== 'production' && state.maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key ) { state.warn( `: component lists rendered with ` + `v-for should have explicit keys. ` + `See https://vuejs.org/guide/list.html#key for more info.`, el.rawAttrsMap['v-for'], true /* tip */ ) } // 标记当前节点上的 v-for 指令已经被处理过了 el.forProcessed = true // avoid recursion // 返回 `_l(exp, function(alias, iterator1, iterator2){return _c(tag, data, children)})` return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + '})'} genIf() 方法 文件位置:src\compiler\codegen\index.js // 处理带有 v-if 指令的节点,最终得到一个三元表达式,condition ? render1 : render2 export function genIf( el: any, state: CodegenState, altGen?: Function, altEmpty?: string): string { // 标记当前节点的 v-if 指令已经被处理过了,避免无效的递归 el.ifProcessed = true // avoid recursion // 得到三元表达式,condition ? render1 : render2 return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)}function genIfConditions( conditions: ASTIfConditions, state: CodegenState, altGen?: Function, altEmpty?: string): string { // 长度若为空,则直接返回一个空节点渲染函数 if (!conditions.length) { return altEmpty || '_e()' } // 从 conditions 数组中拿出第一个条件对象 { exp, block } const condition = conditions.shift() // 返回结果是一个三元表达式字符串,condition ? 渲染函数1 : 渲染函数2 if (condition.exp) { /* 如果 condition.exp 条件成立,则得到一个三元表达式, 如果条件不成立,则通过递归的方式找 conditions 数组中下一个元素, 直到找到条件成立的元素,然后返回一个三元表达式 */ return `(${condition.exp})?${genTernaryExp(condition.block) }:${genIfConditions(conditions, state, altGen, altEmpty) }` } else { return `${genTernaryExp(condition.block)}` } // v-if with v-once should generate code like (a)?_m(0):_m(1) function genTernaryExp(el) { return altGen ? altGen(el, state) : el.once ? genOnce(el, state) : genElement(el, state) }} genSlot() 方法 文件位置:src\compiler\codegen\index.js /* 生成插槽的渲染函数,得到:_t(slotName, children, attrs, bind) */function genSlot(el: ASTElement, state: CodegenState): string { // 插槽名称 const slotName = el.slotName || '"default"' // 生成所有的子节点 const children = genChildren(el, state) // 结果字符串,_t(slotName, children, attrs, bind) let res = `_t(${slotName}${children ? `,function(){return ${children}}` : ''}` const attrs = el.attrs || el.dynamicAttrs ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr =>({/ / slot props are camelized name: camelize (attr.name), value: attr.value, dynamic: attr.dynamic})): null const bind = el.attrsMap [(attrs | | bind) & &! children) {res + = `, null`} if (attrs) {res + =`, ${attrs} `} if (bind) {res + =` ${attrs?':' Null'}, ${bind} `} return res +')'}
GenComponent () method
File location: src\ compiler\ codegen\ index.js
/ * generate rendering functions for dynamic components. Return `c (compName, data, children) `componentName is el.component, take it as argument to shun flow's pessimistic refinement*/function genComponent (componentName: string, el: ASTElement, state: CodegenState): string {/ / all child nodes const children = el.inlineTemplate? Null: genChildren (el, state, true) / / returns `c (compName, data, children) `. CompName is the value of is attribute return` _ c (${componentName}, ${genData (el, state)} ${children? `, ${children}`:'}) `} summary
What is the generation process of the rendering function?
There are two types of renderings generated by the compiler:
Render function, which is responsible for generating the vnode of dynamic nodes
Static rendering function in the staticRenderFns array, which is responsible for generating the vnode of static nodes
The rendering function is generated by traversing AST nodes, processing each node recursively, and finally generating formats such as: _ c (tag, attr, children, normalizationType):
Tag is a label signature
Attr is an attribute object
Children is an array of child nodes, where each element is in the form of _ c (tag, attr, children, normalizationTYpe).
Normalization represents the normalization type of the node, which is a number 0, 1, 2
What do you do with static nodes?
The processing of static nodes consists of two steps:
Put the generate static node vnode function into the staticRenderFns array
Returns an executable function of _ m (idx), that is, executes the function subscribed to idx in the staticRenderFns array to generate the vnode of the static node
How do you handle v-once, v-if, v-for, components, etc.?
Simple v-once nodes are handled in the same way as static nodes.
The processing result of the v-if node is a ternary expression
The processing result of the v-for node is the executable _ l function, which is responsible for generating the vnode of the v-for node
The processing result of the component is the same as that of the ordinary element. The executable code in the form of _ c (compName) is obtained, and the vnode of the component is generated.
Thank you for your reading, the above is the content of "how the vue compiler generates rendering functions". After the study of this article, I believe you have a deeper understanding of how the vue compiler generates rendering functions, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 275
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.