首页
Search
1
解决visual studio code (vscode)安装时没有选择安装路径问题
320 阅读
2
如何在 Clash for Windows 上配置服务
215 阅读
3
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
150 阅读
4
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
149 阅读
5
uniapp打包app提示通讯录权限问题,如何取消通讯录权限
113 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,195
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1195
篇与
的结果
2024-10-20
nextjs 如何不显示next_data的数据
nextjs 提供了 getServerSideProps 方法(之前叫 getInitialProps 方法),用于在渲染页面之前请求数据,但 nextjs 框架为了能够前后端保持同步,会将请求到的数据,通过一个 script 标签传给前端: 但有时候,我们并不想把一些原始数据暴露到前端页面中,如一些博客网站、新闻网站等,基本以展示数据为主,没有同构的需要。所以,如何隐藏掉__NEXT_DATA__中的数据,只展示构建好的 html 呢? 1. 使用方式 # 在 nextjs 的 GitHub 上,有一个 pull request: Allow disabling runtime JS in production for certain pages (experimental) #11949,讨论了这个功能。从nextjs@9.4.0版本开始,为 pages 目录中的组件提供了一个unstable_runtimeJS的配置,当设置该参数为 false 后,则不会再展示__next_data__中的数据。 注意该设置只在process.env.NODE_ENV==='production'时生效。 不过,它带来的副作用也很大,它会导致整个页面实现的前端功能(如点击事件等)全部失效,包括该组件引用的子组件。// pages/home.tsx const Home = ({ nick, age }) => { const handleClick = () => { console.log('home click'); }; return ( home {nick} {age} ); }; // 移除__next_data__,移除所有前端的js export const config = { unstable_runtimeJS: false, }; // https://www.nextjs.cn/docs/basic-features/pages#%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF%E6%B8%B2%E6%9F%93 export const getServerSideProps = async (context: any) => { const { nick, age } = await fetch('api'); return { props: { nick, age } }; }; 最终渲染出来的 html 结构: 2. 副作用 # 从上图中可以看到,unstable_runtimeJS副作用很少很大的,虽不再展示 getServerSideProps 方法中返回的数据,但整个页面所有的 script 标签也都没了。这就造成该组件和它所有的子组件,均没有了前端功能。 3. 解决方案 # 若真的想在前端加载 js,也是有办法的。那就是使用 script 标签,自己加载 js 链接或者内容。 3.1 外链的方式 # 假如我们把要加载的前端 js 放在了https://cdn.bootcdn.net/ajax/libs/axios/0.26.0/axios.js链接中,直接通过 script 标签的 src 引入即可:const Home = ({ nick, age }) => { return ( home {nick} {age} ); }; 3.2 内敛的方式 # 都是同一个项目的代码,单独把某一部分拿出去再构建,实在是不方便。这里可以把 js 代码转成字符串,写到 script 标签中。 3.2.1 dangerouslySetInnerHTML 属性 # 使用 script 标签的 dangerouslySetInnerHTML 属性: console.log(Date.now()));`, }} > 3.2.2 script 标签的内容区 # {`document.querySelector('h1')?.addEventListener('click', () => console.log(Date.now()));`} 3.2.3 利用 function 的 toString() # 方法名调用 toString()会该方法的函数体,这里我们封装成一个立即执行函数的形式:const Home = ({ nick, age }: any) => { // 所有前端要执行的js,放在这里面 const bootstrap = () => { if (typeof document === 'undefined') { return; } document.querySelector('h1')?.addEventListener('click', () => console.log(Date.now())); }; return ( home {nick} {age} ); }; 最终编译后的效果: 4. 总结 # 从上面的分析也能看到,不是特别必要的时候,最好还是不要用unstable_runtimeJS禁用 js,否则为了实现相同的功能,就要用很多的奇技淫巧来弥补。
2024年10月20日
39 阅读
0 评论
0 点赞
2024-10-20
前端中对 url 的一些常用操作
我们平时操作 url 也比较多,如获取 url 中的参数,拼接 url 等,这里我们简单汇总下对 url 的常用操作。 1. 获取 url 中的参数 # 现在浏览器已经支持URLSearchParams对象了,我们可以直接用该方法获取参数。而且该方法的浏览器兼容性也非常好:// 获取当前url中的参数 const getQueryString = (name) => { const searchParams = new URLSearchParams(location.search); return searchParams.get(name); }; 若您的项目要兼容的范围更广,可以参考这篇文章中的方法:javascript 获取 URL 链接和 js 链接中的参数。 2. 获取 url 的域名、路径、referer 等 # 以前我们要获取一个 url 其中的某一部分,还得创建一个a标签来进行解析,如使用 DOM 中的 a 标签解析 url这篇文章中讲解到的。不过现在浏览器也提供了URL对象,获取 url 的某一部分也更简便了。 若只给 URL 传入一个参数,这个 url 必须是绝对地址,即需要携带协议,如 http://, https://, file://等;若不确定第一个参数中是否携带协议和主机信息,我们可以使用第二个参数进行补充。 若我们不确定要解析的参数是不是绝对地址,那就传第 2 个参数进行备用,如:const param = '/post/nodejs/nodejs-http-getquery-qwurz4.html?nick=wenzi'; // 若param是绝对地址,则直接使用,否则用第2个参数进行拼接 // 若拼接后依然不是绝对地址,则抛出TypeError异常 const uu = new URL(param, 'http://localhost'); console.log(uu); 执行后的结果(host 与 hostname 字段的区别:若链接中有端口时,host 字段会拼接上端口;如端口为 8080 时,则 host 字段为localhost:8080,hostname 为localhost):解析后,获取 hostname, path 等就非常方便了,不受其他字段的干扰。具体使用,也可以参考文章如何在 nodejs 的原生 http 服务中获取请求参数。 3. 修改 URL 的某一部分 # 对于一个完整的 URL,有时候我们只想修改其中的某一部分,而不影响其他部分,这个怎么处理呢?比如我们要把链接路径(pathname)中所有的字符a改为字符b,若使用正则的话,则可能把参数和 hash 路由中的字符也给替换掉。或者把当前链接中所有的参数,然后跳转到下一个链接。先把 url 拆分,再进行拼装,其实也可以,但并不是最好的方案。这里用URL的实例化对象,直接更新某一部分,然后最后返回.href属性即可。const replaceUrlPath = (url: string) => { const uu = new URL(url, 'http://localhost'); uu.pathname = uu.pathname.replace(/a/g, 'b'); // 对url的某一部分进行处理 return uu.href; }; replaceUrlPath('https://www.xiabingbao.com/post/javascript/url-handler-r7scei.html?a=b#main?c=123'); // https://www.xiabingbao.com/post/jbvbscript/url-hbndler-r7scei.html?a=b#main?c=123 4. 判断 url 地址是否为绝对地址 # 我们在这里简单地认为只要 url 有协议,它就是绝对地址,无论是什么协议(双斜杠//也算作绝对地址):const isAbsolute = (url) => /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url); 运行的结果:isAbsolute('http://localhost'); // true isAbsolute('https://localhost'); // true isAbsolute('file://localhost'); // true isAbsolute('qqnews://localhost'); // true isAbsolute('//localhost'); // true isAbsolute('/api/list'); // false 5. 拼接 URL # 拼接 url 时,尤其要注意前后的斜杠/:/** * Creates a new URL by combining the specified URLs * * @param {string} baseURL The base URL * @param {string} relativeURL The relative URL * @returns {string} The combined URL */ const combineURLs = (baseURL, relativeURL) => { // 将baseURL最后的斜杠和relativeURL最前面的斜杠去掉 return relativeURL ? `${baseURL.replace(/\/+$/, '')}/${relativeURL.replace(/^\/+/, '')}` : baseURL; }; 使用:combineURLs('https://www.xiabingbao.com/post/', '/request/axios-some-utils.html'); // 得到: https://www.xiabingbao.com/post/request/axios-some-utils.html 我们在这里仅仅是进行简单的拼接,并不保证拼接后的 url 的合法性。 6. http 协议转成 https # 这里用正则判断下就可以,若是以http://开头的,则替换成https://:/** * 将http链接转换为https链接 * @param url 要转换的http链接 */ const http2https = (url: string): string => { // 若不是以 http:// 开头,则原样返回 return url.replace(/^http:\/\//, 'https://'); }; 7. encodeURI 和 encodeURIComponent 的区别 # encodeURI 和 encodeURIComponent 的区别在于前者被设计来用于对完整 URL 进行 URL Encode,于是 URL 中的功能字符,比如&, ?, /, =等等这些并不会被转义;而后者被设计来对一个 URL 中的值进行转义,会把这些功能字符也进行转义。 escape 和 unescape 两个方法已被废弃,应当避免使用。 我们看着例子:const url = 'https://www.xiabingbao.com/post/react/nextjs-server-client-build-qxpzwi.html#1. 只在浏览器端使用的模块'; encodeURI(url); // https://www.xiabingbao.com/post/react/nextjs-server-client-build-qxpzwi.html#1.%20%E5%8F%AA%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AB%AF%E4%BD%BF%E7%94%A8%E7%9A%84%E6%A8%A1%E5%9D%97 encodeURIComponent(url); // https%3A%2F%2Fwww.xiabingbao.com%2Fpost%2Freact%2Fnextjs-server-client-build-qxpzwi.html%231.%20%E5%8F%AA%E5%9C%A8%E6%B5%8F%E8%A7%88%E5%99%A8%E7%AB%AF%E4%BD%BF%E7%94%A8%E7%9A%84%E6%A8%A1%E5%9D%97 encodeURI 编译后的 url 可以正常访问,而 encodeURIComponent 编译后的字符串大部分都是作为参数。
2024年10月20日
36 阅读
0 评论
0 点赞
2024-10-20
前端项目中如何区分环境
一个项目从开发到上线,大致要经过本地开发、测试环境、预发布环境和正式环境。那么在代码中如何区分这几个环境,然后再与对应的后端接口进行对接呢?我们经历多几个项目的磨练后,总结出几个区分不同环境的方法。 从 URL 的域名进行区分; 构建时传入不同的全局变量; 容器提前定义好全局变量(一般后端服务使用); 下面我们来一一进行讲解。我们先来定义几个环境变量:enum EnvType { LOCAL = 'local', // 本地开发环境 TESTING = 'testing', // 测试环境 PRE = 'pre', // 预发布环境 PRODUCTION = 'production', // 正式环境 } 1. 从 URL 的域名进行区分 # 若不同环境使用不同的域名,最简单的就是从 URL 域名进行区分。我们从 URL 中获取到 hostname 或 origin,然后进行判断。// 通过url检测环境 const checkUrlHost = (): EnvType => { const { hostname } = window.location || { hostname: null }; if (/test/.test(hostname)) { return EnvType.TESTING; } if (/pre/.test(hostname)) { return EnvType.PRE; } if (/local/.test(hostname)) { return EnvType.LOCAL; } return EnvType.PRODUCTION; }; 注意:不要用整个 URL 地址来进行匹配,因为中间的路径或者携带的参数,可能会干扰到判断。最开始我们还不能区分域名时,都是在同一个域名里进行开发,然后用不同的路径来区分,不过代码里用的是location.href来区分。然后我们参数里要带着用户的昵称,这个用户的昵称正好有个test的字样。导致项目把这个链接识别成了测试环境,然后就去请求测试环境的接口了。 2. 构建时传入不同的全局变量 # 现在我们发布项目时,都会先进行构建,然后再把 html 发布出去。那么我们就可以在构建时,把当前要发布的环境对应的变量传进去。如何传入全局变量,还得看您正在使用的框架,如 React 的脚手架 create-react-app,传入的全局变量必须以REACT_APP_开头;Vue 的脚手架 vue-cli,传入的全局变量必须以VUE_APP_开头。我们以 create-react-app 为例,使用REACT_APP_SITE_ENV来指定所在的环境。构建时:# 本地开发环境 $ REACT_APP_SITE_ENV=local npm run start # 测试环境 $ REACT_APP_SITE_ENV=testing npm run build # 预发布环境 $ REACT_APP_SITE_ENV=pre npm run build # 正式环境 $ npm run build # 不传入 $ REACT_APP_SITE_ENV=production npm run build # 或传入production 代码中获取该全局变量:// 通过全局变量来检测环境 const getEnv = (): EnvType => { const { REACT_APP_SITE_ENV }: any = process.env; // 命令行传入 // REACT_APP_SITE_ENV只能是EnvType中的值 // 若没有传入,或者不是这里面的几个值,则返回production if (REACT_APP_SITE_ENV) { // eslint-disable-next-line no-restricted-syntax for (const key in EnvType) { const env = (EnvType as any)[key]; if (env === REACT_APP_SITE_ENV) { return REACT_APP_SITE_ENV; } } } return EnvType.PRODUCTION; }; 这里我们在构建流水线里指定 REACT_APP_SITE_ENV 为不同的值即可。 3. 容器提前定义好全局变量 # 前端代码一般是只有构建过程,但后端代码(如 nodejs 等),通常是【启动】或者【构建 + 启动】的过程,若有构建过程,则像上面那样在流水线里指定全局变量即可。而启动这个程序一般是在容器里启动的,而流水线里的全局变量在构建完成后就失效了,留不到启动这一步。因此,若是在容器中启动的程序,您可以在容器里配置好全局变量,这样程序在启动时,就自动能够获取到了。至于怎么配置,还得看您公司自己的配置。如我们的配置是: 4. 总结 # 这里我们主要讲解了下如何在项目中来区分环境,很多开发者还会再额外地定义一些变量或方法,来直接使用,如:// 定义变量 export const IS_LOCALENV = ENV === EnvType.LOCAL; // 是否是本地环境 export const IS_TESTENV = ENV === EnvType.TESTING; // 是否测试环境 export const IS_PREENV = ENV === EnvType.PRE; // 是否是预发布环境 export const IS_PRODENV = ENV === EnvType.PRODUCTION; // 是否是正式环境 // 定义方法 export const isPro = () => ENV === EnvType.PRODUCTION; // 是否是正式环境 或许未来新的框架或者部署流程,还是出现新的定义环境的方式。
2024年10月20日
7 阅读
0 评论
0 点赞
2024-10-20
第7页
Front-end Engineer,前端开发工程师目前是一名前端开发工程师,主要负责前端规划、框架与架构、前端性能优化。专注前端技术,关注交互体验,擅长web ajax开发。坚信前端工程师的价值是最终能把技术和设计完美结合在一起。用最新的技术方案巧妙地帮助这些设计得以实现。
2024年10月20日
7 阅读
0 评论
0 点赞
2024-10-20
如何将评论数据从扁平数组结构转为树形结构
我们在之前的文章 如何实现一个楼中楼的评论系统,主要讲解了楼中楼评论系统的设计初衷、数据库表的结构,但没有讲解从数据库中拿到数据后,如何转成楼中楼的结构。 1. 定义下数据结构 # 我是使用的 mysql 数据库,mysql 数据库中是以行为单位,来存储数据的。我们从数据库中拿到的数据是一个数组结构,大致如下,我最初考虑楼中楼最多只嵌套一层,因此加了一个pid字段用来表示该评论在哪个评论里:/** * 从数据库中查询到一篇文章里的所有评论 * id: 当前评论的id * pid: 当前评论在哪个评论里,0表示是顶层评论 * replyid: 该评论回复的是哪个评论,0表示是顶层评论 * content: 内容 */ const list = [ { id: 1, pid: 0, replyid: 0, nick: '111', content: '' }, { id: 2, pid: 0, replyid: 0, nick: '222', content: '' }, { id: 3, pid: 2, replyid: 2, nick: '333', content: '' }, { id: 4, pid: 2, replyid: 3, nick: '444', content: '' }, { id: 5, pid: 2, replyid: 4, nick: '555', content: '' }, { id: 6, pid: 1, replyid: 1, nick: '666', content: '' }, { id: 7, pid: 2, replyid: 3, nick: '777', content: '' }, ]; 接下来,让我们通过代码来实现下数组转为树(楼中楼)的结构。 2. 嵌套一层的楼中楼 # 我们先实现一个只嵌套一层的树结构。我们要实现的结构是这样的,若直接回复上层的评论,则省略该评论回复的是谁,若回复的是楼中楼的评论,则展示出回复的是哪条:- 1 |- 6 - 2 |- 3 |- 4 -> 3 |- 5 -> 4 |- 7 -> 3 我们分析下 list 中数据的特点: 每条数据都有个pid和replyid两个字段,指向他的父级,但若 pid 和 replyid 为 0 时,则表示该评论自己就是最顶层的评论; 后一条评论回复之前的评论,pid 和 replyid 两个字段的值一定是存在的(不可能回复一条不存在的评论); 依托于 js 中的对象引用的特性:在不同的地方操作相同的对象,所有使用该对象的数据都会发生变化。其实这并不是一个好的特性,但在这里特别好使。我们可以直接把后面的数据添加到前面的数据字段中。const listToTree = (list) => { const newList = JSON.parse(JSON.stringify(list)); // 避免影响外层的数组 const map = new Map(); const result = []; newList.forEach((comment) => { map.set(comment.id, comment); if (comment.pid) { // 楼中楼的评论 const parentComment = map.get(comment.pid); // 回复的该评论,该评论是一定存在的 if (!parentComment.children) { parentComment.children = []; // 通过对象引用,可以直接修改之前的数据 } if (comment.pid !== comment.replyid) { comment.replyNick = map.get(comment.replyid).nick; } parentComment.children.push(comment); } else { result.push(comment); } }); return result; }; 最终实现的效果的示意图: 3. 不限制层数的树结构 # 若我们不限制树结构的层数,该怎么实现呢?我们在这里把 list 数组的结构稍微改下,去掉 pid 的干扰,并再添加几条数据:/** * 从数据库中查询到一篇文章里的所有评论 * id: 当前评论的id * replyid: 该评论回复的是哪个评论,0表示是顶层评论 * content: 内容 */ const list = [ { id: 1, replyid: 0, nick: '111', content: '' }, { id: 2, replyid: 0, nick: '222', content: '' }, { id: 3, replyid: 2, nick: '333', content: '' }, { id: 4, replyid: 3, nick: '444', content: '' }, { id: 5, replyid: 4, nick: '555', content: '' }, { id: 6, replyid: 1, nick: '666', content: '' }, { id: 7, replyid: 3, nick: '777', content: '' }, { id: 8, replyid: 5, nick: '888', content: '' }, { id: 9, replyid: 8, nick: '999', content: '' }, { id: 10, replyid: 9, nick: 'aaa', content: '' }, { id: 11, replyid: 10, nick: 'bbb', content: '' }, ]; 其实不限制树结构的层数,要比固定的层数简单一些,不用判断是否要折叠树结构,一直追加下去即可。 3.1 使用循环的方式 # 我们顺着上面循环的方式,来实现下不限制层数的树结构。const listToTreeDeep = (list) => { const newList = JSON.parse(JSON.stringify(list)); // 避免影响之前的数组 const map = new Map(); const result = []; newList.forEach((comment) => { map.set(comment.id, comment); if (comment.replyid) { // 楼中楼的评论 const parentComment = map.get(comment.replyid); // 回复的该评论,该评论是一定存在的 if (!parentComment.children) { parentComment.children = []; } // 这里按照层级来表示回复关系,不再获取要回复的哪个评论 // if (comment.pid !== comment.replyid) { // comment.replyNick = map.get(comment.replyid).nick; // } parentComment.children.push(comment); } else { result.push(comment); } }); return result; }; 最终实现的效果示意图:具体效果可以查看实现的 demo:【 数组转树结构的自定义层数的循环实现方式 】。 3.2 使用递归的方式 # 无限嵌套的结构也可以使用递归的方式,但这并不是最好的方式,因为每次递归都要用当前 id 循环查找一次。我们在这里用代码实现一次,但不推荐这种方式。/** * @param {any[]} list 评论列表 * @param {number} replyid 回复的那个评论的id **/ const listToTreeRecursion = (list, replyid = 0) => { const newList = JSON.parse(JSON.stringify(list)); // 避免影响之前的数组 const result = []; newList.forEach((comment) => { if (comment.replyid === replyid) { comment.children = listToTreeRecursion(list, comment.id); result.push(comment); } }); return result; }; 递归中我们不使用额外的数据来存储数据,是因为每次的执行,都会从头循环一遍。 4 自定义层数的树结构 # 若再复杂一点,可以自定义层数 n,前 n-1 层一直向内嵌套,最后的第 n 层采用平铺的方式。即把前面的两种展示方式进行结合。这里我们依然分成两部分来讲解。 4.1 使用循环的方式 # 上面不限制层数时,直接往其父级的 children 属性中追加即可。但现在可以自定义层数后,就要在循环时,判断当前节点的深度,若该节点的深度小于 设定的层数 n ,则添加到其父级的 children 中;否则就得添加到其深度为 n-1 的祖先节点的 children 里。我在这里使用了两个字段来进行标记: deep: 当前节点的深度; pid: 该节点所属的父级节点是哪个; 具体的代码实现:/** * @param {any[]} list 评论列表 * @param {number} maxPath 自定义的层数 **/ const listToTreeSelf = (list, maxPath = 4) => { const newList = JSON.parse(JSON.stringify(list)); const map = new Map(); const result = []; newList.forEach((comment) => { map.set(comment.id, comment); if (comment.replyid) { // 楼中楼的评论 const parentComment = map.get(comment.replyid); comment.deep = parentComment.deep + 1; // 当前深度是父级节点的深度+1 if (comment.deep >= maxPath) { // 若当前节点的深度超过了设定的最大深度 // 则该节点不能挂载在其父节点了, // 通过父节点的pid查找父节点挂载在哪个节点上, // 该节点也挂载上面 const ancestorComment = map.get(parentComment.pid); comment.replyNick = parentComment.nick; if (!ancestorComment.children) { ancestorComment.children = []; } // 当前节点挂载的节点 comment.pid = ancestorComment.id; ancestorComment.children.push(comment); } else { // 没有超过设定的最大深度 // 挂载在其父节点上 if (!parentComment.children) { parentComment.children = []; } comment.pid = parentComment.id; parentComment.children.push(comment); } } else { comment.deep = 0; result.push(comment); } }); return result; }; 假设我们设定的最大层数为 4,最终的效果是: 4.2 使用递归的方式 # 在使用递归的方式来构建自定义层数的树形结构时,当深度达到设定的最大层级时,就需要转为循环了。然后开始查找该节点下所有的子节点和子孙节点。 注意:这里不要遗漏子孙节点。 const listToTreeSelfRecursion = (list, maxPath = 4, replyid = 0) => { const newList = JSON.parse(JSON.stringify(list)); // 避免影响之前的数组 const result = []; newList.forEach((comment) => { if (maxPath child.id === comment.replyid); if (child) { comment.replyNick = child.nick; result.push(comment); } } } else { // 还可以继续递归 if (comment.replyid === replyid) { comment.children = listToTreeSelfRecursion(list, maxPath - 1, comment.id); result.push(comment); } } }); return result; }; 5. 不规则的数组 # 我们在上面所有的循环实现中,都默认了父节点在前面,毕竟对评论系统而言,肯定现有的父级评论,才能有当前评论。但比如我们把场景扩展到无限嵌套的部门列表中时,就不一定有序了。最近新创建了一个中间部门,然后把下面所有的部门都归属到这个中间部门里。就会出现父级节点在后面的情况。这里我们只以不限层数的循环实现方式为例,看下若父级节点在后面,如何实现:// 把数据调换下位置 const list = [ { id: 6, pid: 1, replyid: 1, nick: '666', content: '' }, { id: 7, pid: 2, replyid: 3, nick: '777', content: '' }, { id: 8, pid: 2, replyid: 5, nick: '888', content: '' }, { id: 9, pid: 2, replyid: 8, nick: '999', content: '' }, { id: 10, pid: 2, replyid: 9, nick: 'aaa', content: '' }, { id: 11, pid: 2, replyid: 10, nick: 'bbb', content: '' }, { id: 1, pid: 0, replyid: 0, nick: '111', content: '' }, { id: 2, pid: 0, replyid: 0, nick: '222', content: '' }, { id: 3, pid: 2, replyid: 2, nick: '333', content: '' }, { id: 4, pid: 2, replyid: 3, nick: '444', content: '' }, { id: 5, pid: 2, replyid: 4, nick: '555', content: '' }, ]; const listToTreeDeep = (list) => { const newList = JSON.parse(JSON.stringify(list)); // 避免影响之前的数组 const map = new Map(); const result = []; newList.forEach((comment) => { // 可能先把该节点的children存储起来 // 这里把之前存储的数据合并下,然后再存储起来 comment = { ...comment, ...map.get(comment.id) }; map.set(comment.id, comment); if (comment.replyid) { // 楼中楼的评论 const parentComment = map.get(comment.replyid) || {}; // 以空的Object兜底 if (!parentComment.children) { parentComment.children = []; } parentComment.children.push(comment); map.set(comment.replyid, parentComment); } else { result.push(comment); } }); return result; }; 可见在乱序的情况下,逻辑就要多一些。在可行的情况,我们把父级节点放在前面,但若实现起来比较困难,上面的那种方式也是可行的。 6. 总结 # 我们从最开始的二层楼中楼结构,扩展到了无限层级,最终扩展到了可以自定义层级。就目前递归的实现方式来看,并没有循环的性能好,因为每次递归都要循环一遍数据,若在数组节点比较多时,递归的性能就会直线下降。下一篇文章,我们再讨论下,如何把树形结构转成数组结构。
2024年10月20日
7 阅读
0 评论
0 点赞
1
...
74
75
76
...
239