JS 手写代码 程序员最重要的就是动手能力�? ::: tip 如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务 �?专业、系统、高效、全流程 准备前端面试 :::
手写深拷�? 考虑循环引用
::: details 参考答�? 简单的深拷贝:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function cloneDeep (source, hash = new WeakMap () ) { if (!isObject (source)) return source if (hash.has (source)) return hash.get (source) var target = Array .isArray (source) ? [] : {} hash.set (source, target) for (var key in source) { if (Object .prototype .hasOwnProperty .call (source, key)) { if (isObject (source[key])) { target[key] = cloneDeep (source[key], hash) } else { target[key] = source[key] } } } return target }
考虑更多比如爆栈的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 function cloneDeep (x ) { const root = {} const loopList = [ { parent : root, key : undefined , data : x, }, ] while (loopList.length ) { const node = loopList.pop () const parent = node.parent const key = node.key const data = node.data let res = parent if (typeof key !== 'undefined' ) { res = parent[key] = {} } for (let k in data) { if (data.hasOwnProperty (k)) { if (typeof data[k] === 'object' ) { loopList.push ({ parent : res, key : k, data : data[k], }) } else { res[k] = data[k] } } } } return root }
参考阅读:
手写 getType 函数 获取详细的变量类�? ::: details 参考答�?
1 2 3 4 5 function getType (data ) { }
:::
手写 class 继承 在某网页中,有三种菜单:button menu,select menu,modal menu�? 他们的共同特点:
都有 title icon 属�?- 都有 isDisabled 方法(可直接返回 false�?- 都有 exec 方法,执行菜单的逻辑
他们的不同点�?
button menu,执�?exec 时打�?'hello'
select menu,执�?exec 时返回一个数�?['item1', 'item2', 'item3']
modal menu,执�?exec 时返回一�?DOM Element <div>modal</div>
请用 ES6 语法写出这三种菜单的 class
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class BaseMenu { constructor (title, icon ) { this .title = title this .icon = icon } isDisabled ( ) { return false } } class ButtonMenu extends BaseMenu { constructor (title, icon ) { super (title, icon) } exec ( ) { console .log ('hello' ) } } class SelectMenu extends BaseMenu { constructor (title, icon ) { super (title, icon) } exec ( ) { return ['item1' , 'item2' , 'item3' ] } } class ModalMenu extends BaseMenu { constructor (title, icon ) { super (title, icon) } exec ( ) { const div = document .createElement ('div' ) div.innerText = 'modal' return div } }
:::
手写防抖 Debounce ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function debounce (func, wait, immediate ) { var timeout, result var debounced = function ( ) { var context = this var args = arguments if (timeout) clearTimeout (timeout) if (immediate) { var callNow = !timeout timeout = setTimeout (function ( ) { timeout = null }, wait) if (callNow) result = func.apply (context, args) } else { timeout = setTimeout (function ( ) { func.apply (context, args) }, wait) } return result } debounced.cancel = function ( ) { clearTimeout (timeout) timeout = null } return debounced }
参考阅读:
手写截流 Throttle ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function throttle (func, wait, options ) { var timeout, context, args, result var previous = 0 if (!options) options = {} var later = function ( ) { previous = options.leading === false ? 0 : new Date ().getTime () timeout = null func.apply (context, args) if (!timeout) context = args = null } var throttled = function ( ) { var now = new Date ().getTime () if (!previous && options.leading === false ) previous = now var remaining = wait - (now - previous) context = this args = arguments if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout (timeout) timeout = null } previous = now func.apply (context, args) if (!timeout) context = args = null } else if (!timeout && options.trailing !== false ) { timeout = setTimeout (later, remaining) } } throttled.cancel = function ( ) { clearTimeout (timeout) previous = 0 timeout = null } return throttled }
参考阅读:
手写 bind ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Function .prototype .bind2 = function (context ) { if (typeof this !== 'function' ) { throw new Error ('Function.prototype.bind - what is trying to be bound is not callable' ) } var self = this var args = Array .prototype .slice .call (arguments , 1 ) var fNOP = function ( ) {} var fBound = function ( ) { var bindArgs = Array .prototype .slice .call (arguments ) return self.apply (this instanceof fNOP ? this : context, args.concat (bindArgs)) } fNOP.prototype = this .prototype fBound.prototype = new fNOP () return fBound }
参考阅读:
:::
手写 call �?apply ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 Function .prototype .call2 = function (context ) { var context = context || window context.fn = this var args = [] for (var i = 1 , len = arguments .length ; i < len; i++) { args.push ('arguments[' + i + ']' ) } var result = eval ('context.fn(' + args + ')' ) delete context.fn return result } Function .prototype .apply = function (context, arr ) { var context = Object (context) || window context.fn = this var result if (!arr) { result = context.fn () } else { var args = [] for (var i = 0 , len = arr.length ; i < len; i++) { args.push ('arr[' + i + ']' ) } result = eval ('context.fn(' + args + ')' ) } delete context.fn return result }
参考阅读:
:::
手写 EventBus 自定义事�? ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class EventBus { constructor ( ) { this .eventObj = {} this .callbcakId = 0 } $on(name, callbcak) { if (!this .eventObj [name]) { this .eventObj [name] = {} } const id = this .callbcakId ++ this .eventObj [name][id] = callbcak return id } $emit(name, ...args) { const eventList = this .eventObj [name] for (const id in eventList) { eventList[id](...args) if (id.indexOf ('D' ) !== -1 ) { delete eventList[id] } } } $off(name, id) { delete this .eventObj [name][id] if (!Object .keys (this .eventObj [name]).length ) { delete this .eventObj [name] } } $once(name, callbcak) { if (!this .eventObj [name]) { this .eventObj [name] = {} } const id = 'D' + this .callbcakId ++ this .eventObj [name][id] = callbcak return id } }
参考阅读:
手写数组拍平 Array Flatten ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function flatten (input, shallow, strict, output ) { output = output || [] var idx = output.length for (var i = 0 , len = input.length ; i < len; i++) { var value = input[i] if (shallow) { var j = 0 , length = value.length while (j < length) output[idx++] = value[j++] } else { flatten (value, shallow, strict, output) idx = output.length } } else if (!strict) { output[idx++] = value } } return output }
参考阅读:
手写解析 URL 参数�?JS 对象 ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function parseParam (url ) { const paramsStr = /.+\?(.+)$/ .exec (url)[1 ] let paramsObj = {} if (/=/ .test (param)) { val = decodeURIComponent (val) val = /^\d+$/ .test (val) ? parseFloat (val) : val if (paramsObj.hasOwnProperty (key)) { } } else { } }) return paramsObj }
参考阅读:
手写数组去重 ::: details 参考答�?
1 var unique = (a ) => [...new Set (a)]
参考阅读:
手写红绿�? 模拟一个红绿灯变化,红�?1 秒,绿灯 1 秒,黄灯 1 秒,然后循环
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function red ( ) { console .log ('red' ) } function green ( ) { console .log ('green' ) } function yellow ( ) { console .log ('yellow' ) } function light (cb, wait ) { return new Promise ((resolve ) => { setTimeout (() => { cb () resolve () }, wait) }) } function start ( ) { return Promise .resolve () .then (() => { return light (red, 1000 ) }) .then (() => { return light (green, 1000 ) }) .then (() => { return light (yellow, 1000 ) }) .finally (() => { return start () }) } start ()
:::
手写 Promise ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class MyPromise { this .initBind () executor (this .resolve , this .reject ) } initBind ( ) { this .resolve = this .resolve .bind (this ) this .reject = this .reject .bind (this ) } initValue ( ) { resolve (value ) { this .PromiseState = 'fulfilled' } reject (reason ) { this .PromiseState = 'rejected' this .PromiseResult = reason } }
参考阅读:
手写 Promise.all ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static all (promises ) { const result = [] let count = 0 return new MyPromise ((resolve, reject ) => { const addData = (index, value ) => { result[index] = value count++ if (count === promises.length ) resolve (result) } promises.forEach ((promise, index ) => { if (promise instanceof MyPromise ) { promise.then (res => { addData (index, res) }, err => reject (err)) } else { addData (index, promise) } }) }) }
参考阅读:
手写 Promise.race ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static race (promises ) { return new MyPromise ((resolve, reject ) => { promises.forEach (promise => { if (promise instanceof MyPromise ) { promise.then (res => { resolve (res) }, err => { reject (err) }) } else { resolve (promise) } }) }) }
参考阅读:
手写 Promise.allSettled ::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static allSettled (promises ) { return new Promise ((resolve, reject ) => { const res = [] let count = 0 const addData = (status, value, i ) => { res[i] = { status, value } count++ if (count === promises.length ) { resolve (res) } } promises.forEach ((promise, i ) => { if (promise instanceof MyPromise ) { promise.then (res => { addData ('fulfilled' , res, i) }, err => { addData ('rejected' , err, i) }) } else { addData ('fulfilled' , promise, i) } }) }) }
参考阅读:
手写一�?LazyMan 实现 sleep 机制 1 2 3 4 5 6 7 8 LazyMan ('Tony' ).eat ('breakfast' ).sleep (3 ).eat ('lunch' ).sleep (1 ).eat ('dinner' )
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class LazyMan { constructor (name ) { this .name = name this .tasks = [] this .tasks .push (() => { console .log (`Hi I am ${name} ` ) return Promise .resolve () }) this .runTasks () }, 0 ) } async runTasks ( ) { for (const task of this .tasks ) { await task () } } eat (food ) { this .tasks .push (() => { console .log (`I am eating ${food} ` ) return Promise .resolve () }) return this } sleep (seconds ) { this .tasks .push (() => { console .log (`等待${seconds} �?..` ) return new Promise ((resolve ) => { setTimeout (resolve, seconds * 1000 ) }) }) return this } } return new LazyMan (name) }
:::
手写 curry 函数,实现函数柯里化 ::: details 参考答�?
基础版本实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function curry (fn ) { return function curried (...args ) { if (args.length >= fn.length ) { return fn.apply (this , args) } return curried.apply (this , args.concat (args2)) } } } function add (a, b, c ) { return a + b + c } const curriedAdd = curry (add)console .log (curriedAdd (1 )(2 )(3 )) console .log (curriedAdd (1 , 2 )(3 )) console .log (curriedAdd (1 )(2 , 3 ))
支持占位符的进阶版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function curry (fn, placeholder = '_' ) { const length = fn.length return function curried (...args ) { const checkFilled = (args ) => { return filledArgsCount >= length } const mergeArgs = (existingArgs, newArgs ) => { const result = [...existingArgs] let newArgsIndex = 0 if (result[i] === placeholder) { result[i] = newArgs[newArgsIndex++] } } } const mergedArgs = mergeArgs (args, []) if (checkFilled (mergedArgs)) { const finalArgs = mergedArgs.slice (0 , length).filter ((arg ) => arg !== placeholder) return fn.apply (this , finalArgs) } return curried.apply (this , mergeArgs (mergedArgs, nextArgs)) } } } const add = (a, b, c ) => a + b + cconst curriedAdd = curry (add)const _ = '_' console .log (curriedAdd (1 )(2 )(3 )) console .log (curriedAdd (1 , 2 )(3 )) console .log (curriedAdd (1 )(_, 3 )(2 )) console .log (curriedAdd (_, 2 )(1 )(3 )) console .log (curriedAdd (_, _, 3 )(1 )(2 ))
ES6 简化版�?1 2 3 4 5 6 7 8 9 10 11 12 const curry = (fn, arity = fn.length ) => { const curried = (...args ) => (args.length >= arity ? fn (...args) : (...more ) => curried (...args, ...more)) return curried } const sum = (a, b, c ) => a + b + cconst curriedSum = curry (sum)console .log (curriedSum (1 )(2 )(3 )) console .log (curriedSum (1 , 2 )(3 )) console .log (curriedSum (1 )(2 , 3 ))
:::
手写 compose 函数 ::: details 参考答�? compose 函数是函数式编程中的一个重要概念,它将多个函数组合成一个函数,从右到左执行�?
基础实现(使�?reduce�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function compose (...fns ) { if (fns.length === 0 ) return (arg ) => arg if (fns.length === 1 ) return fns[0 ] return fns.reduce ( (a, b ) => (...args ) => a (b (...args)) ) } const add1 = (x ) => x + 1 const multiply2 = (x ) => x * 2 const addThenMultiply = compose (multiply2, add1)console .log (addThenMultiply (5 ))
支持异步函数的实�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function composeAsync (...fns ) { if (fns.length === 0 ) return (arg ) => arg if (fns.length === 1 ) return fns[0 ] return fns.reduce ((a, b ) => async (...args) => { const result = await b (...args) return a (result) }) } const asyncAdd = async (x ) => { await new Promise ((resolve ) => setTimeout (resolve, 1000 )) return x + 1 } const asyncMultiply = async (x ) => { await new Promise ((resolve ) => setTimeout (resolve, 1000 )) return x * 2 } const asyncOperation = composeAsync (asyncMultiply, asyncAdd)asyncOperation (5 ).then ((result ) => console .log (result))
从左到右执行�?pipe 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function pipe (...fns ) { if (fns.length === 0 ) return (arg ) => arg if (fns.length === 1 ) return fns[0 ] return fns.reduce ( (a, b ) => (...args ) => b (a (...args)) ) } const addOne = (x ) => x + 1 const multiplyTwo = (x ) => x * 2 const addThenMultiplyPipe = pipe (addOne, multiplyTwo)console .log (addThenMultiplyPipe (5 ))
带错误处理的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function composeWithError (...fns ) { if (fns.length === 0 ) return (arg ) => arg if (fns.length === 1 ) return fns[0 ] return fns.reduce ((a, b ) => (...args ) => { try { const result = b (...args) return a (result) } catch (error) { console .error ('Error in compose:' , error) throw error } }) } const divide = (x ) => { if (x === 0 ) throw new Error ('Cannot divide by zero' ) return 10 / x } const square = (x ) => x * xconst divideAndSquare = composeWithError (square, divide)console .log (divideAndSquare (2 )) try { divideAndSquare (0 ) } catch (e) { console .log ('Caught error:' , e.message ) }
使用场景示例�?
数据转换管道 �?
1 2 3 4 5 6 const toLowerCase = (str ) => str.toLowerCase ()const removeSpaces = (str ) => str.replace (/\s/g , '' )const addPrefix = (str ) => `prefix_${str} ` const processString = compose (addPrefix, removeSpaces, toLowerCase)console .log (processString ('Hello World' ))
数学计算 �?
1 2 3 4 5 6 const double = (x ) => x * 2 const addTen = (x ) => x + 10 const square = (x ) => x * xconst calculate = compose (square, addTen, double)console .log (calculate (5 ))
**数据处理�?*�?
1 2 3 4 5 6 const filterEven = (arr ) => arr.filter ((x ) => x % 2 === 0 )const multiplyAll = (arr ) => arr.map ((x ) => x * 2 )const sum = (arr ) => arr.reduce ((a, b ) => a + b, 0 )const processNumbers = compose (sum, multiplyAll, filterEven)console .log (processNumbers ([1 , 2 , 3 , 4 , 5 , 6 ]))
注意事项�?
compose 函数从右到左执行,�?pipe 函数从左到右执行
确保函数的输入输出类型匹�?3. 处理异步操作时需要使�?async/await 版本
考虑错误处理机制
函数组合应该保持纯函数的特�? compose 函数是函数式编程中的重要工具,它能够帮助我们构建更加模块化和可维护的代码。通过组合小的、单一功能的函数,我们可以构建出复杂的数据转换管道�? :::
手写一�?LRU 缓存 ::: details 参考答�? LRU(Least Recently Used)是一种缓存淘汰策略,它会优先删除最近最少使用的数据。下面提供两种实现方式:使用 Map 的简单实现和不使�?Map 的基础实现�?
使用 Map 的实�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class LRUCache { constructor (capacity ) { this .cache = new Map () this .capacity = capacity } get (key ) { if (!this .cache .has (key)) return -1 const value = this .cache .get (key) this .cache .delete (key) this .cache .set (key, value) return value } put (key, value ) { this .cache .delete (key) } this .cache .delete (firstKey) } this .cache .set (key, value) } } const cache = new LRUCache (2 )cache.put (1 , 1 ) cache.put (2 , 2 ) console .log (cache.get (1 )) cache.put (3 , 3 ) console .log (cache.get (2 ))
使用双向链表的实现(不依�?Map�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 class Node { constructor (key, value ) { this .key = key this .value = value this .prev = null this .next = null } } class LRUCache { constructor (capacity ) { this .capacity = capacity this .cache = {} this .count = 0 this .head = new Node (0 , 0 ) this .tail = new Node (0 , 0 ) this .head .next = this .tail this .tail .prev = this .head } this .removeNode (node) this .addToHead (node) } removeNode (node ) { node.prev .next = node.next node.next .prev = node.prev } node.prev = this .head node.next = this .head .next this .head .next .prev = node this .head .next = node } removeTail ( ) { const node = this .tail .prev this .removeNode (node) return node } get (key ) { if (key in this .cache ) { const node = this .cache [key] this .moveToHead (node) return node.value } return -1 } put (key, value ) { if (key in this .cache ) { const node = this .cache [key] node.value = value this .moveToHead (node) } else { this .cache [key] = newNode this .addToHead (newNode) this .count ++ const tail = this .removeTail () delete this .cache [tail.key ] this .count -- } } } } const cache = new LRUCache (2 )cache.put (1 , 1 ) cache.put (2 , 2 ) console .log (cache.get (1 )) cache.put (3 , 3 ) console .log (cache.get (2 )) cache.put (4 , 4 ) console .log (cache.get (1 )) console .log (cache.get (3 )) console .log (cache.get (4 ))
实现原理说明�?
Map 实现版本 �?
利用 Map 的特性,它能够记住键的原始插入顺�? - get 操作时将访问的元素移到最后(最新使用)
put 操作时如果超出容量,删除第一个元素(最久未使用�?
双向链表实现版本 �? - 使用哈希表实�?O(1) 的查�? - 使用双向链表维护数据的使用顺�? - 最近使用的数据放在链表头部
性能分析�?
**时间复杂�?*�?
**空间复杂�?*�? - O(capacity),其�?capacity 是缓存的容量
使用场景�?
**浏览器缓�?*�?
1 2 3 const browserCache = new LRUCache (100 )browserCache.put ('url1' , 'response1' ) browserCache.put ('url2' , 'response2' )
内存缓存 �?
1 2 3 const memoryCache = new LRUCache (1000 )memoryCache.put ('userId1' , userDataObject1) memoryCache.put ('userId2' , userDataObject2)
**数据库查询缓�?*�?
1 2 3 4 5 6 7 8 9 const queryCache = new LRUCache (50 )function query (sql ) { const cached = queryCache.get (sql) if (cached !== -1 ) return cached const result = executeQuery (sql) queryCache.put (sql, result) return result }
:::
使用 Vue3 Composable 组合式函数,实现 useCount 1 const { count } = useCount ()
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import { ref, onMounted, onUnmounted } from 'vue' export function useCount ( ) { const count = ref (0 ) let timer = null timer = setInterval (() => { count.value ++ }, 1000 ) } startCount () }) onUnmounted (() => { if (timer) { clearInterval (timer) } }) return { count, } }
:::
使用 Vue3 Composable 组合式函数,实现 useRequest 1 const { loading, data, error } = useRequest (url)
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { ref } from 'vue' export function useRequest (url ) { const data = ref (null ) const loading = ref (false ) const error = ref (null ) const fetchData = async ( ) => { loading.value = true error.value = null try { const response = await fetch (url) if (!response.ok ) { throw new Error (`HTTP error! status: ${response.status} ` ) } data.value = await response.json () } catch (e) { error.value = e } finally { loading.value = false } } fetchData () return { data, loading, error, } }
:::
使用 React Hook 实现 useCount
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { useState, useEffect } from 'react' function useTimer ( ) { const [count, setCount] = useState (0 ) useEffect (() => { const timer = setInterval (() => { setCount ((prev ) => prev + 1 ) }, 1000 ) }, []) return { count } } export default useTimer
:::
使用 React Hook 实现 useRequest 1 const { loading, data, error } = useRequest (url)
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import { useState, useEffect } from 'react' function useRequest (url ) { const [data, setData] = useState (null ) const [loading, setLoading] = useState (true ) const [error, setError] = useState (null ) useEffect (() => { const fetchData = async ( ) => { setLoading (true ) setError (null ) try { const response = await fetch (url) if (!response.ok ) { throw new Error (`HTTP error! status: ${response.status} ` ) } const result = await response.json () setData (result) } catch (e) { setError (e) } finally { setLoading (false ) } } fetchData () }, [url]) return { data, loading, error } } export default useRequest
:::
手写 VNode 对象,表示如�?DOM 节点 1 2 3 4 <div class ="container" > <img src ="x1.png" /> <p > hello</p > </div >
::: details 参考答�?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const vnode = { tag : 'div' , props : { class : 'container' , }, children : [ { tag : 'img' , props : { src : 'x1.png' , }, }, { tag : 'p' , props : {}, children : ['hello' ], }, ], }
:::