干货
React
Vue
工程化
前瞻
life.caoover 3 years

如何使用Xcode调试web在iOS10.x.x兼容问题(含破解文件)

一、问题概述 开发中遇到 iOS10.x.x 版本的兼容问题,找不到古董版本真机,新版 Xcode 里 Simulator 中没有了 iOS10.x.x ,找到方法破解,如下: 1.Xcode配置 新建项目后,发现新版Xcode设备里是iphone8,且系统是13.0 或 15.0 使用以下步骤把屏蔽的旧机型和系统显示出来 2.替换Info.plist文件 打开终端cd 到如下目录,然后open ./打开当前目录,如图 /Library/Developer/CoreSimulator/Profiles/Runtimes 选中iOS 10.3.simruntime 右键选中 显示包内容,打开Contents目录,替换掉Info.plist文件 Info.plist 3.下载此文件,覆盖后,重启(Xcode),还不行,重启电脑,重启大法好 重启后,打开Xcode,打开 demo项目 选中自己的PROJECT, Target选中你想要的版本,比如10.0,这时候就出现久违的古董机型和系统了 4.配置完成,以后无需打开Xcode,直接选中Simulator即可,Device 会有OS 10.3 二、利用mac Safari浏览器像调试pc一样调试模拟器内的web 步骤跟调试真机很相似,熟悉的同学可略过 1.打开需要调试的模拟器和系统(eg: iphone6 10.3) 打开模拟器里的Safari,输入想要调试的web url 2.打开Safari浏览器,并打开"开发菜单" 3.选中"开发"Tab,找到想要调试页面 4.网页检查器出现,调试起来吧

612
life.caoabout 2 years

Web Components原生支持的组件化

一、前因 十一国庆期间,看到大街小巷的红旗飘飘,于是乎想到,可否用H5做个动画国旗,发现了个有趣的东东, 一个html静态文件,出现了一个自定义组件?炫酷的动画,有点撩,很难不想了解它一下点击体验,不慎入坑Web Components。 二、 fancy-components 国旗动画的来源 HTML <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>fancy-components</title> </head> <body> <fc-china></fc-china> <script type="module"> import { FcChina } from './fancy-components/index.js' // new 就相当于全局注册了这个组件,相当于 Vue 的 Vue.component('FcChina', 组件) new FcChina() </script> </body> </html> fancy-components:直译 - 花式组件库,一个 Web Components** 组件库**。接下来介绍下到底什么是 Web Components吧。 三、什么是 Web Components Google一直在推动的浏览器 原生组件 ,即 Web Components API。 它就是为了解决"组件化"而诞生的,它是浏览器原生支持的组件化,不依赖任何库、依赖和打包工具就可以在浏览器中运行。Web Components 不是一门单一的技术,而是四门技术的组合,这四门技术分别是: Shadow DOM HTML templates Custom Elements HTML Imports(已废弃不再介绍) Shadow DOM和 HTML templates( and slots) 类似 Vue 的 Slot,不作详解,有兴趣的同学可点击上面来自MDN的链接学习。下面就介绍里面最重要的一项技术,同时也是所有浏览器都没有提出反对意见,一致通过的一项技术 —— Custom Elements** (自定义元素)**。 四、Custom Elements 基础使用 它使开发者能够将 HTML 页面 的功能封装为 custom elements(自定义标签),window 全局对象上有一个 customElements 提供自定义元素支持,它包含四个 API: define:注册/定义自定义元素 get:获取自定义元素的构造函数 whenDefined upgrade define 注册组件(自定义元素),不能写成自闭和标签,浏览器会当作起始标签包裹后面的全部内容 HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Custom Elements</title> </head> <body> <!-- 使用元素 --> <huo-la-la></huo-la-la> <!-- 不能写成自闭和标签,浏览器会当作起始标签包裹后面的全部内容 --> <!-- <huo-la-la /> --> <script> // 注册组件(自定义元素) window.customElements.define( // 参数1:元素名,必须包含一个短横线,以区分原生元素 'huo-la-la', // 参数2:用于定义元素行为的类(类似 React 中的类组件),必须继承自 HTMLElement class extends HTMLElement { constructor() { super() // custome elements 类中的 this 指向组件本身 console.log(this) this.innerHTML = '<h1>货拉拉</h1>' this.onclick = () => alert('啥车都有') } } ) // 多次注册相同名称的组件会报错: // the name "huo-la-la" has already been used with this registry // window.customElements.define('huo-la-la', class extends HTMLElement {}) </script> </body> </html> get _获取第三方组件的构造函数,用于继承扩展,_给上面国旗案例扩展一个点击事件 HTML <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>fancy-components-click</title> </head> <body> <my-fc-china click></my-fc-china> <script type="module"> // 要在服务器环境下运行,建议 http-server import { FcChina } from './fancy-components/index.js' // new 就相当于全局注册了这个组件,相当于 Vue 的 Vue.component('FcChina', 组件) new FcChina() // 获取第三方组件的构造函数,用于继承扩展 const FcChinaConstructor = customElements.get('fc-china') customElements.define( 'my-fc-china', class extends FcChinaConstructor { constructor() { super() this.onclick = () => console.log('自定义点击事件') } } ) </script> </body> </html> whenDefined whenDefined是元素定义后触发的回调,接口返回Promise,通常用于异步注册组件的时候 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>whenDefined</title> </head> <body> <huo-la-la>加载中...</huo-la-la> <script> // 模拟 JS 代码执行延迟 setTimeout(() => { customElements.define( 'huo-la-la', class extends HTMLElement { } ) }, 2000) // 返回一个 Promise customElements .whenDefined('huo-la-la') .then(() => { document.querySelector('huo-la-la').innerHTML = '货拉拉' }) .catch(err => console.log(err)) </script> </body> </html> upgrade 如果在定义元素之前先使用 JS 创建了元素,则元素实例并不是继承的定义元素行为的类,可以使用upgrade HTML <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>upgrade</title> </head> <body> <script> // 先使用 JS 创建自定义元素 const el = document.createElement('huo-la-la') // 再注册自定义元素 class Huolala extends HTMLElement { } customElements.define('huo-la-la', Huolala) // 返回 false console.log(el instanceof Huolala) // 升级元素 customElements.upgrade(el) // 返回 true console.log(el instanceof Huolala) </script> </body> </html> 五、Web Components 对 Vue 的影响 潜看一段Vue和Web Components引入组件的对比 Vue 组件 my-span.vue <!-- my-span.vue --> <template> <span>my-span</span> </template> <script> export default {}; </script> <style> span { color: purple; } </style> 只需要将它稍微修改一下,它就会变成 Web Components 文件,能够直接在浏览器中运行,不用像vue那么复杂需要node、webpack、loader等打包后,浏览器才能识别。 Web Components组件 my-span.html HTML <!-- my-span.html --> <template> <span>my-span</span> </template> <script> // 获取 DOM 元素 const dom = document.currentScript.ownerDocument.querySelector('template').content // 有点像 React 定义组件的写法 class MySpan extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).appendChild(dom) } } // 注册组件 customElements.define('my-span', MySpan) </script> <style> span { color: purple; } </style> 使用 HTML Imports 在 HTML 页面中引入组件: HTML <!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <!-- HTML Imports --> <link rel="import" href="my-span.html"> </head> <body> <my-span></my-span> </body> </html> 当看到这里的时候,感觉,这不就是跟vue很像,不对,应该是vue跟Web Components很像很像,其实并不是巧合,尤大在创作Vue时,大量参考了Web Components的语法。 上文提到HTML Imports已废弃,上文只是为了对比当时跟vue的相似度,加以修改,只需要用js生成dom即可,my-span.html 改为my-span.js // my-span.js class MySpan extends HTMLElement { constructor() { super() this.render() } // 生成 HTML 和 CSS render() { const shadow = this.attachShadow({ mode: 'open' }) const dom = document.createElement('span') const style = document.createElement('style') // dom.textContent = 'my-span' dom.innerHTML = ` <slot>默认内容</slot> <slot name="content">默认内容</slot> ` style.textContent = ` span { color: purple; } ` shadow.appendChild(style) shadow.appendChild(dom) } } // 注册组件 customElements.define('my-span', MySpan) index.html <!-- index.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <!-- HTML Imports(已废弃) --> <!-- <link rel="import" href="my-span.html"> --> <!-- ES Modules --> <script type="module" src="my-span.js"></script> </head> <body> <my-span> <h1>默认插槽</h1> <h2 slot="content">具名插槽</h2> </my-span> </body> </html> slot的使用是不是vue也跟Web Components很像。Vue和React的官网也提到了和Web Components的关系,Vue 与 Web Components、在React 中使用 Web Components。 六、推荐 Web Components 的一些学习路径 本文也参考了如下的文档和博客内容,想深入学习的同学可参考: 官方-需梯子 MDN Web Docs Web Component可以取代你的前端框架吗? 本文demo的代码包: web-componets.zip 注:使用 ES Modules ,都需要开启一个 Web 服务,使用 VS Code 的 Live Server 插件或者使用http-server。

223
life.caoalmost 5 years

Gridsome 四部曲之介绍 (一)

是什么 GitHub 仓库:https://github.com/gridsome/gridsome 官网:https://gridsome.org/ Gridsome 是由Vue.js驱动的Jamstack框架,用于构建默认情况下快速生成的静态生成的网站和应用。 Gridsome是Vue提供支持的静态站点生成器,用于为任何无头CMS,本地文件或API构建可用于CDN的网站 使用Vue.js,webpack和Node.js等现代工具构建网站。通过npm进行热重载并访问任何软件包,并使用自动前缀在您喜欢的预处理器(如Sass或Less)中编写CSS。 基于 Vue.js 的 Jamstack 框架 Gridsome 使开发人员可以轻松构建默认情况下快速生成的静态生成的网站和应用程序 Gridsome允许在内容里面引用任何CMS或数据源。 从WordPress,Contentful或任何其他无头CMS或API中提取数据,并在组件和页面中使用GraphQL访问它。 为什么选择 Gridsome Vue.js for frontend - The simplest & most approachable frontend framework. Data sourcing - Use any Headless CMSs, APIs or Markdown-files for data. Local development with hot-reloading - See code changes in real-time. File-based page routing - Any Name.vue file in src/pages is a static route. Dynamic routing - Any [param].vue file in src/pages is a dynamic route. Static file generation - Deploy securely to any CDN or static web host. GraphQL data layer - Simpler data management with a centralized data layer. Automatic Code Splitting - Builds ultra performance into every page. Plugin ecosystem - Find a plugin for any job. 什么是 Jamstack Gridsome是一个Jamstack框架。 Jamstack使您可以通过预渲染文件并直接从CDN直接提供文件来构建快速安全的站点和应用程序,而无需管理或运行Web服务器。 Learn more about the Jamstack. 它是如何工作的 Gridsome生成静态HTML,一旦加载到浏览器中,该HTML就会渗入Vue SPA。这意味着您可以使用Gridsome构建静态网站和动态应用程序。 Gridsome为每个页面构建一个.html文件和一个.json文件。加载第一页后,它仅使用.json文件来预取和加载下一页的数据。它还为需要它的每个页面构建一个.js包(代码拆分)。 它使用vue-router进行SPA路由,并使用vue-meta来管理。 Gridsome默认添加最小57kB的gzip JS捆绑包大小(vue.js,vue-router,vue-meta和一些用于图像延迟加载的文件)。 详细了解其工作原理 学习条件 您应该具有有关HTML,CSS,Vue.js以及如何使用终端的基本知识。了解GraphQL的工作原理是有好处的,但不是必需的。 Gridsome是学习它的好方法。 Gridsome 需要Node.js(v8.3 +),并建议使用 Yarn。 备选方案 VuePress Nuxt Gatsby.js 使用场景 不适合管理系统 简单页面展示 想要有更好的 SEO 想要有更好的渲染性能

36
life.caoalmost 5 years

Gridsome 四部曲之起步 (二)

起步 目标:快速了解 Gridsome 项目 1、安装 Gridsome CLI # 使用 yarn yarn global add @gridsome/cli # 使用 npm npm install --global @gridsome/cli # 查看是否安装成功 gridsome --version 2、创建 Gridsome 项目 # 创建项目 gridsome create my-gridsome-site # 进入项目中 cd my-gridsome-site # 启动开发模式,或 npm run develop gridsome develop gridsome 项目安装依赖注意事项: 配置 node-gyp 编译环境 https://github.com/nodejs/node-gyp 配置环境变量:npm_config_sharp_libvips_binary_host 为 https://npm.taobao.org/mirrors/sharp-libvips/ https://github.com/lovell/sharp-libvips https://developer.aliyun.com/mirror/NPM https://npm.taobao.org/mirrors https://sharp.pixelplumbing.com/installnpm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp" npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips" 配置 hosts:199.232.68.133 raw.githubusercontent.com https://www.ipaddress.com/ 3、目录结构 . ├── src │ ├── components # 公共组件 │ ├── layouts # 布局组件 │ ├── pages # 页面路由组件 │ ├── templates # 模板文件 │ ├── favicon.png # 网站图标 │ └── main.js # 应用入口 ├── static # 静态资源存储目录,该目录中的资源不做构建处理 ├── README.md ├── gridsome.config.js # 应用配置文件 ├── gridsome.server.js # 针对服务端的配置文件 ├── package-lock.json └── package.json 4、自己试一试 在 src/pages 目录中创建一个 .vue 组件 5、构建 gridsome build 构建结果默认输出到 dist 目录中。 Gridsome 会把每个路由文件构建为独立的 HTML 页面。 6、部署 可以把构建结果 dist 放到任何 Web 服务器中进行部署。 例如我们这里使用 Node.js 命令行工具 serve 来测试构建结果。 npm install -g serve serve dist 或者可以部署到其它第三方托管平台:https://gridsome.org/docs/deployment/。 或是自己的服务器,都可以! 核心概念 目标:学习 Gridsome 的核心概念 Pages 通过在 src/pages 文件夹中添加Vue组件来创建页面。他们使用基于文件的路由系统。例如,src / pages / About.vue将是 mywebsite.com/about/。页面用于简单页面和列出集合的页面(例如/ blog /)。 了解有关页面的更多信息:https://gridsome.org/docs/pages/。 Collections 如果您要在网站上放置博客文章,标签,产品等,则收藏很有用。可以使用 Source插件或 Data Store API 从任何Headless CMS,内容API或Markdown文件中获取集合。 集合存储在临时的本地GraphQL数据层中,可以在任何地方查询,过滤,分页或有关系。 Templates 模板负责显示集合的节点(单个页面)。模板通常位于src / templates中。如果未在模板配置中指定组件,则Gridsome尝试查找与集合名称相同的文件。 这是一个例子: <!-- src/templates/Post.vue --> <template> <Layout> <h1 v-html="$page.post.title" /> </Layout> </template> <page-query> query ($id: ID!) { post(id: $id) { title } } </page-query> 更多关于 Templates 的内容:https://gridsome.org/docs/templates/。 Layouts 布局是在页面和模板内部用于包装内容的Vue组件。布局通常包含页眉和页脚。 页面中通常按以下方式使用布局: <template> <Layout> <h1>About us</h1> </Layout> </template> <script> import Layout from '~/layouts/Default.vue' export default { components: { Layout } } </script> 也可以在全局范围内使用布局,因此您无需每页导入它们。 请注意,Gridsome CLI创建的默认模板将使用全局布局组件。 更多关于 Layouts 的内容:https://gridsome.org/docs/layouts/。 Images Gridsome具有内置的 <g-image> 组件,可输出优化的逐行图像。如果更改宽度和高度,则在开发时还可以实时调整大小和裁剪。 <g-images> 创建一个超小型模糊的嵌入式base64图像,然后在视图中使用IntersectionObserver延迟加载图像。 更多关于 Images 的内容:https://gridsome.org/docs/images/。 Linking Gridsome具有内置的 <g-link> 组件,该组件在查看链接时使用 IntersectionObserver 来预取链接的页面。这使得在 Gridsome 站点中浏览非常快,因为单击的页面已经下载。 更多关于 <g-link> 的内容:https://gridsome.org/docs/linking/。 部署 https://gridsome.org/docs/deployment/

24
life.caoalmost 5 years

Gridsome 四部曲之基础 (三)

Gridsome 基础 目录结构 . ├── package.json # 包说明文件 ├── gridsome.config.js # Gridsome 配置文件 ├── gridsome.server.js # 自定义 Gridsome 编译 ├── static/ # 静态资源存储目录,该目录中的资源不做构建处理 └── src/ ├── main.js # 应用入口 ├── index.html # 公共页面 ├── App.vue # 根组件 ├── layouts/ # 布局组件 │ └── Default.vue ├── pages/ # 路由页面 │ ├── Index.vue │ └── Blog.vue └── templates/ # 模板 └── BlogPost.vue 项目配置 Gridsome需要 gridsome.config.js 才能工作。插件和项目设置位于此处。基本配置文件如下所示: module.exports = { siteName: 'Gridsome', siteUrl: 'https://www.gridsome.org', plugins: [] } 属性 类型 默认值 说明 siteName string <dirname> 该名称通常在标题标签中使用。 siteDescription string '' 页面描述,<meta name="description" content="xxx"> pathPrefix string '' Gridsome假定您的项目是从域的根目录提供的。如果您的项目将托管在名为my-app的子目录中,则将此选项更改为“ / my-app”。 titleTemplate string %s - <siteName> 设置标题标签的模板。 %s占位符将替换为您在页面中设置的metaInfo的标题。 plugins Array [] 通过将插件添加到plugins数组来激活插件。 templates object {} 定义 collections 的路由和模板。 metadata object {} 将全局元数据添加到GraphQL模式。 icon string | object './src/favicon.png' Gridsome默认情况下会将位于src / favicon.png的任何图像用作favicon和touchicon,但您可以定义其他路径或大小等。图标应为正方形且至少16个像素。网站图标将调整为16、32、96像素。默认情况下,触摸图标的大小将调整为76、152、120、167、180像素。 configureWebpack object | Function 如果该选项是一个对象,它将与内部配置合并。 chainWebpack Function 该函数将接收由webpack-chain驱动的ChainableConfig实例。 runtimeCompiler boolean false 在运行时包括Vue模板编译器。 configureServer Function 配置开发服务器。 permalinks.trailingSlash boolean true 默认情况下,在页面和模板后添加斜杠。启用此选项后,具有动态路由的页面将不包含尾部斜杠,并且服务器上必须具有额外的重写规则才能正常工作。另外,的静态路径不会自动包含尾部斜杠,而应包含在路径中: permalinks.slugify 使用自定义的Slugify方法。默认是 @sindresorhus/slugify css.split boolean false 将CSS分成多个块。默认情况下禁用拆分。拆分CSS可能会导致奇怪的行为。 css.loaderOptions Object {} 将选项传递给与CSS相关的 loader host string localhost port number 8080 outputDir string ‘dist’ 运行gridsome构建时将在其中生成生产构建文件的目录。 插件示例: module.exports = { plugins: [ { use: '@gridsome/source-filesystem', options: { path: 'blog/**/*.md', route: '/blog/:year/:month/:day/:slug', typeName: 'Post' } } ] } 注意事项: 开发过程中修改配置需要重启服务 Pages 页面 页面负责在URL上显示您的数据。每个页面将静态生成,并具有自己的带有标记的index.html文件。 在Gridsome中创建页面有两种选择: 单文件组件 使用 Pages API 以编程方式创建页面 pages 中的单文件组件 src/pages 目录中的单文件组件将自动具有其自己的URL。文件路径用于生成 URL,以下是一些基本示例: src/pages/Index.vue becomes /(The frontpage) src/pages/AboutUs.vue becomes /about-us/ src/pages/about/Vision.vue becomes /about/vision/ src/pages/blog/Index.vue becomes /blog/ 大小自动转小写,驼峰命名会自动使用短横杠分割 src/pages 中的页面通常用于诸如 /about/ 之类的固定 URL,或用于在 /blog/ 等处列出博客文章。 使用 Pages API 创建页面 可以使用 gridsome.server.js 中的 createPages 钩子以编程方式创建页面。如果您要从外部 API 手动创建页面而不使用 GraphQL 数据层,则此功能很有用。 module.exports = function (api) { api.createPages(({ createPage }) => { createPage({ path: '/my-page', component: './src/templates/MyPage.vue' }) }) } 动态路由 动态路由对于仅需要客户端路由的页面很有用。例如,根据URL中的细分从生产环境中的外部API获取信息的页面。 通过文件创建动态路由 动态页面用于客户端路由。可以通过将名称包装在方括号中来将路由参数放置在文件和目录名称中。例如: src/pages/user/[id].vue becomes /user/:id. src/pages/user/[id]/settings.vue becomes /user/:id/settings. 注意事项: 在构建时,这将生成 user/_id.html 和 user/_id/settings.html,并且您必须具有重写规则以使其正常运行。 具有动态路由的页面的优先级低于固定路由。例如,如果您有一个 /user/create 路由和 /user/:id 路由,则 /user/create 路由将具有优先级。 这是一个基本的页面组件,它使用路由中的id参数来获取客户端的用户信息: <template> <div v-if="user"> <h1>{{ user.name }}</h1> </div> </template> <script> export default { data() { return { user: null } }, async mounted() { const { id } = this.$route.params const response = await fetch(`https://api.example.com/user/${id}`) this.user = await response.json() } } </script> 始终使用 mounted 来获取客户端数据。由于在生成静态HTML时执行数据,因此在 created 中获取数据会引起问题。 通过编程方式创建动态路由 以编程方式创建带有动态路由的页面,以获取更高级的路径。动态参数使用 : 来指定。 每个参数都可以具有一个自定义的正则表达式,以仅匹配数字或某些值。 module.exports = function (api) { api.createPages(({ createPage }) => { createPage({ path: '/user/:id(\\d+)', component: './src/templates/User.vue' }) }) } 生成重写规则 Gridsome无法为动态路由的每种可能的变体生成HTML文件,这意味着直接访问URL时最有可能显示404页。而是,Gridsome生成一个HTML文件,该文件可用于重写规则。例如,类似/ user /:id的路由将生成位于/user/_id.html的HTML文件。您可以具有重写规则,以将所有与/ user /:id匹配的路径映射到该文件。 由于每种服务器类型都有自己的语法,因此必须手动生成重写规则。 afterBuild 挂钩中的 redirects 数组包含应生成的所有必要的重写规则。 const fs = require('fs') module.exports = { afterBuild ({ redirects }) { for (const rule of redirects) { // rule.from - The dynamic path // rule.to - The HTML file path // rule.status - 200 if rewrite rule } } } 页面 meta 信息 Gridsome 使用 vue-meta 处理有关页面的元信息。 <template> <div> <h1>Hello, world!</h1> </div> </template> <script> export default { metaInfo: { title: 'Hello, world!', meta: [ { name: 'author', content: 'John Doe' } ] } } </script> 自定义 404 页面 创建一个 src/pages/404.vue 组件以具有一个自定义 404 页面。 Collections 集合 集合是一组节点,每个节点都包含带有自定义数据的字段。如果您要在网站上放置博客文章,标签,产品等,则集合很有用。 添加集合 集合可以通过 source plugins 添加,也可以使用 Data Store API 自己添加。 在开发和构建期间,这些集合存储在本地内存数据存储中。节点可以来自本地文件(Markdown,JSON,YAML等)或任何外部API。 Collections 使用 source plugins 添加集合 将集合添加到 Gridsome 的最简单方法是使用源插件。本示例从 WordPress 网站创建集合。源插件的 typeName 选项通常用于为插件添加的集合名称添加前缀。 // gridsome.config.js module.exports = { plugins: [ { use: '@gridsome/source-wordpress', options: { baseUrl: 'YOUR_WEBSITE_URL', typeName: 'WordPress', } } ] } 你可以在这里浏览插件列表。 使用 Data Store API 添加集合 您可以从任何外部 API 手动添加集合。 本示例创建一个名为 Post 的集合,该集合从 API 获取内容并将结果作为节点添加到该集合中。 // gridsome.server.js const axios = require('axios') module.exports = function (api) { api.loadSource(async actions => { const collection = actions.addCollection('Post') const { data } = await axios.get('https://api.example.com/posts') for (const item of data) { collection.addNode({ id: item.id, title: item.title, content: item.content }) } }) } 了解有关 Data Store API 的更多信息。

29
life.caoalmost 5 years

Gridsome 四部曲之处理数据GraphQL (四)

处理数据 大家可能会有疑惑,不是建静态博客么,怎么会有 GraphQL?难道还要部署服务器? 其实这里 GraphQL 并不是作为服务器端部署,而是作为 Gridsome 在本地管理资源的一种方式。 通过 GraphQL 统一管理实际上非常方便,因为作为一个数据库查询语言,它有非常完备的查询语句,与 JSON 相似的描述结构,再结合 Relay 的 Connections 方式处理集合,管理资源不再需要自行引入其它项目,大大减轻了维护难度。 GraphQL数据层 Import data GraphQL数据层是在开发模式下可用的工具。这是临时存储到 Gridsome 项目中的所有数据的地方。可以将其视为可帮助您更快更好地处理数据的本地数据库。 来自 GraphQL 数据层的数据将生成为静态内容。 数据层和导入数据的源之间没有实时连接。这意味着您需要重新生成网站以获取最新的数据更新。 如果需要动态数据,则应使用客户端数据。 提示:默认情况下,Pages 也 Site metadata 已添加到数据层。 处理数据 How to import data. How to query data. How to filter data. How to create taxonomy pages. How to paginate data. How to add client-side / dynamic data. GraphQL资源管理器 每个 Gridsome 项目都有一个 GraphQL 资源管理器,可以在开发模式下使用它来探索和测试查询。 在这里,您还将获得所有可用 GraphQL 集合的列表。 通常可以通过转到 http:// localhost:8080/___explore 来打开它。 graphql-explorer 导入数据 Gridsome 使您可以将数据从任何数据源导入 GraphQL 数据层。 使用 source plugins 使用外部 API 使用本地文件 Markdown Images YAML CSV JSON 查询数据 您可以将数据从GraphQL数据层查询到任何页面,模板或组件中。在Vue组件中,使用 <page-query> 或 <static-query> 块添加查询。 在 Pages 和 Templates 中使用 <page-query> 在 Components 中使用 <static-query> 如何使用 GraphQL 查询 在 Gridsome 中使用 GraphQL 很容易,并且您不需要了解 GraphQL。 这是一个如何在页面的 page-query 中使用GraphQL的示例: <template> <div> <div v-for="edge in $page.posts.edges" :key="edge.node.id"> <h2>{{ edge.node.title }}</h2> </div> </div> </template> <page-query> query { posts: allWordPressPost { edges { node { id title } } } } </page-query> 使用 GraphQL,您仅查询所需的数据。这使得处理数据更加容易和整洁。 查询总是从 query 开始 然后是 Posts(可以是任何东西) 然后写一些内容例如 posts: allWordPressPost。 allWordPressPost 是您要查询的GraphQL集合的名称。 posts: 部分是可选的别名。 使用 posts 作为别名时,您的数据将位于 $page.posts(如果使用 <static-query>,则为 $static.posts)。否则,它将在 $page.allWordPressPost 上可用。 学习更多关于 GraphQL 查询的内容:https://graphql.org/learn/queries/。

27
life.caoover 1 year

Virtual DOM和Virtual DOM库——Vue虚拟dom的借鉴

什么是 Virtual DOM **Virtual DOM(虚拟 DOM)**,是由普通的 JS 对象来描述 DOM 对象,因为不是真实的 DOM 对象,所以叫 Virtual DOM 真实 DOM 成员 let element = document.querySelector('#app') let s = '' for (var key in element) { s += key + ',' } console.log(s) // 打印结果 align,title,lang,translate,dir,hidden,accessKey,draggable,spellcheck,autocapitalize,contentEditable,isContentEditable,inputMode,offsetParent,offsetTop,offsetLeft,offsetWidth,offsetHeight,style,innerText,outerText,oncopy,oncut,onpaste,onabort,onblur,oncancel,oncanplay,oncanplaythrough,onchange,onclick,onclose,oncontextmenu,oncuechange,ondblclick,ondrag,ondragend,ondragenter,ondragleave,ondragover,ondragstart,ondrop,ondurationchange,onemptied,onended,onerror,onfocus,oninput,oninvalid,onkeydown,onkeypress,onkeyup,onload,onloadeddata,onloadedmetadata,onloadstart,onmousedown,onmouseenter,onmouseleave,onmousemove,onmouseout,onmouseover,onmouseup,onmousewheel,onpause,onplay,onplaying,onprogress,onratechange,onreset,onresize,onscroll,onseeked,onseeking,onselect,onstalled,onsubmit,onsuspend,ontimeupdate,ontoggle,onvolumechange,onwaiting,onwheel,onauxclick,ongotpointercapture,onlostpointercapture,onpointerdown,onpointermove,onpointerup,onpointercancel,onpointerover,onpointerout,onpointerenter,onpointerleave,onselectstart,onselectionchange,onanimationend,onanimationiteration,onanimationstart,ontransitionend,dataset,nonce,autofocus,tabIndex,click,focus,blur,enterKeyHint,onformdata,onpointerrawupdate,attachInternals,namespaceURI,prefix,localName,tagName,id,className,classList,slot,part,attributes,shadowRoot,assignedSlot,innerHTML,outerHTML,scrollTop,scrollLeft,scrollWidth,scrollHeight,clientTop,clientLeft,clientWidth,clientHeight,attributeStyleMap,onbeforecopy,onbeforecut,onbeforepaste,onsearch,elementTiming,previousElementSibling,nextElementSibling,children,firstElementChild,lastElementChild,childElementCount,onfullscreenchange,onfullscreenerror,onwebkitfullscreenchange,onwebkitfullscreenerror,setPointerCapture,releasePointerCapture,hasPointerCapture,hasAttributes,getAttributeNames,getAttribute,getAttributeNS,setAttribute,setAttributeNS,removeAttribute,removeAttributeNS,hasAttribute,hasAttributeNS,toggleAttribute,getAttributeNode,getAttributeNodeNS,setAttributeNode,setAttributeNodeNS,removeAttributeNode,closest,matches,webkitMatchesSelector,attachShadow,getElementsByTagName,getElementsByTagNameNS,getElementsByClassName,insertAdjacentElement,insertAdjacentText,insertAdjacentHTML,requestPointerLock,getClientRects,getBoundingClientRect,scrollIntoView,scroll,scrollTo,scrollBy,scrollIntoViewIfNeeded,animate,computedStyleMap,before,after,replaceWith,remove,prepend,append,querySelector,querySelectorAll,requestFullscreen,webkitRequestFullScreen,webkitRequestFullscreen,createShadowRoot,getDestinationInsertionPoints,ELEMENT_NODE,ATTRIBUTE_NODE,TEXT_NODE,CDATA_SECTION_NODE,ENTITY_REFERENCE_NODE,ENTITY_NODE,PROCESSING_INSTRUCTION_NODE,COMMENT_NODE,DOCUMENT_NODE,DOCUMENT_TYPE_NODE,DOCUMENT_FRAGMENT_NODE,NOTATION_NODE,DOCUMENT_POSITION_DISCONNECTED,DOCUMENT_POSITION_PRECEDING,DOCUMENT_POSITION_FOLLOWING,DOCUMENT_POSITION_CONTAINS,DOCUMENT_POSITION_CONTAINED_BY,DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC,nodeType,nodeName,baseURI,isConnected,ownerDocument,parentNode,parentElement,childNodes,firstChild,lastChild,previousSibling,nextSibling,nodeValue,textContent,hasChildNodes,getRootNode,normalize,cloneNode,isEqualNode,isSameNode,compareDocumentPosition,contains,lookupPrefix,lookupNamespaceURI,isDefaultNamespace,insertBefore,appendChild,replaceChild,removeChild,addEventListener,removeEventListener,dispatchEvent 可以使用 Virtual DOM 来描述真实 DOM,示例 { sel: "div", data: {}, children: undefined, text: "Hello Virtual DOM", elm: undefined, key: undefined } 为什么使用 Virtual DOM 手动操作 DOM 比较麻烦,还需要考虑浏览器兼容性问题,虽然有 jQuery 等库简化 DOM 操作,但是随着项目的复杂 DOM 操作复杂提升 为了简化 DOM 的复杂操作于是出现了各种 MVVM 框架,MVVM 框架解决了视图和状态的同步问题 为了简化视图的操作我们可以使用模板引擎,但是模板引擎没有解决跟踪状态变化的问题,于是 Virtual DOM 出现了 Virtual DOM 的好处是当状态改变时不需要立即更新 DOM,只需要创建一个虚拟树来描述 DOM, Virtual DOM 内部将弄清楚如何有效(diff)的更新 DOM 参考 github 上 virtual-dom 的描述 虚拟 DOM 可以维护程序的状态,跟踪上一次的状态 通过比较前后两次状态的差异更新真实 DOM 虚拟 DOM 的作用 维护视图和状态的关系 复杂视图情况下提升渲染性能 除了渲染 DOM 以外,还可以实现 SSR(Nuxt.js/Next.js)、原生应用(Weex/React Native)、小程序(mpvue/uni-app)等 image-20200102104642121 Virtual DOM 库 SnabbdomVue 2.x 内部使用的 Virtual DOM 就是改造的 Snabbdom 大约 200 SLOC(single line of code) 通过模块可扩展 源码使用 TypeScript 开发 最快的 Virtual DOM 之一 virtual-dom 案例演示 jQuery-demo snabbdom-demo

39
life.caoover 1 year

Snabbdom 基本使用——学习Vue Virtual DOM的最佳入门库

Snabbdom 基本使用 创建项目 打包工具为了方便使用 parcel 创建项目,并安装 parcel # 创建项目目录 md snabbdom-demo # 进入项目目录 cd snabbdom-demo # 创建 package.json npm init -y # 本地安装 parcel npm install parcel-bundler -D 配置 package.json 的 scripts "scripts": { "dev": "parcel index.html --open", "build": "parcel build index.html" } 创建目录结构 │ index.html │ package.json └─src 01-basicusage.js 导入 Snabbdom Snabbdom 文档 看文档的意义 学习任何一个库都要先看文档 通过文档了解库的作用 看文档中提供的示例,自己快速实现一个 demo 通过文档查看 API 的使用 文档地址 https://github.com/snabbdom/snabbdom 当前版本 v2.1.0 # --depth 表示克隆深度, 1 表示只克隆最新的版本. 因为如果项目迭代的版本很多, 克隆会很慢 git clone -b v2.1.0 --depth=1 https://github.com/snabbdom/snabbdom.git 安装 Snabbdom 安装 Snabbdom npm install snabbdom@2.1.0 导入 Snabbdom Snabbdom 的两个核心函数 init 和 h() init() 是一个高阶函数,返回 patch() h() 返回虚拟节点 VNode,这个函数我们在使用 Vue.js 的时候见过 import { init } from 'snabbdom/init' import { h } from 'snabbdom/h' const patch = init([]) 注意:此时运行的话会告诉我们找不到 init / h 模块,因为模块路径并不是 snabbdom/int,这个路径是在 package.json 中的 exports 字段设置的,而我们使用的打包工具不支持 exports 这个字段,webpack 4 也不支持,webpack 5 支持该字段。该字段在导入 snabbdom/init 的时候会补全路径成 snabbdom/build/package/init.js "exports": { "./init": "./build/package/init.js", "./h": "./build/package/h.js", "./helpers/attachto": "./build/package/helpers/attachto.js", "./hooks": "./build/package/hooks.js", "./htmldomapi": "./build/package/htmldomapi.js", "./is": "./build/package/is.js", "./jsx": "./build/package/jsx.js", "./modules/attributes": "./build/package/modules/attributes.js", "./modules/class": "./build/package/modules/class.js", "./modules/dataset": "./build/package/modules/dataset.js", "./modules/eventlisteners": "./build/package/modules/eventlisteners.js", "./modules/hero": "./build/package/modules/hero.js", "./modules/module": "./build/package/modules/module.js", "./modules/props": "./build/package/modules/props.js", "./modules/style": "./build/package/modules/style.js", "./thunk": "./build/package/thunk.js", "./tovnode": "./build/package/tovnode.js", "./vnode": "./build/package/vnode.js" } 如果使用不支持 package.json 的 exports 字段的打包工具,我们应该把模块的路径写全查看安装的 snabbdom 的目录结构 import { h } from 'snabbdom/build/package/h' import { init } from 'snabbdom/build/package/init' import { classModule } from 'snabbdom/build/package/modules/class' 回顾 Vue 中的 render 函数 new Vue({ router, store, render: h => h(App) }).$mount('#app') thunk() 是一种优化策略,可以在处理不可变数据时使用 代码演示 基本使用 import { h } from 'snabbdom/build/package/h' import { init } from 'snabbdom/build/package/init' // 使用 init() 函数创建 patch() // init() 的参数是数组,将来可以传入模块,处理属性/样式/事件等 let patch = init([]) // 使用 h() 函数创建 vnode let vnode = h('div.cls', [ h('h1', 'Hello Snabbdom'), h('p', '这是段落') ]) const app = document.querySelector('#app') // 把 vnode 渲染到空的 DOM 元素(替换) // 会返回新的 vnode let oldVnode = patch(app, vnode) setTimeout(() => { vnode = h('div.cls', [ h('h1', 'Hello World'), h('p', '这是段落') ]) // 把老的视图更新到新的状态 oldVnode = patch(oldVnode, vnode) // h('!') 是创建注释 patch(oldVnode, h('!')) }, 2000) 模块 Snabbdom 的核心库并不能处理 DOM 元素的属性/样式/事件等,如果需要处理的话,可以使用模块 常用模块 官方提供了 6 个模块 attributes设置 DOM 元素的属性,使用 setAttribute() 处理布尔类型的属性 props和 attributes 模块相似,设置 DOM 元素的属性 element[attr] = value 不处理布尔类型的属性 class切换类样式 注意:给元素设置类样式是通过 sel 选择器 dataset设置 data-* 的自定义属性 eventlisteners注册和移除事件 style设置行内样式,支持动画 delayed/remove/destroy 模块使用 模块使用步骤:导入需要的模块 init() 中注册模块 使用 h() 函数创建 VNode 的时候,可以把第二个参数设置为对象,其他参数往后移 代码演示 import { h } from 'snabbdom/build/package/h' import { init } from 'snabbdom/build/package/init' // 导入需要的模块 import { styleModule } from 'snabbdom/build/package/modules/style' import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners' // 使用 init() 函数创建 patch() // init() 的参数是数组,将来可以传入模块,处理属性/样式/事件等 let patch = init([ // 注册模块 styleModule, eventListenersModule ]) // 使用 h() 函数创建 vnode let vnode = h('div.cls', { // 设置 DOM 元素的行内样式 style: { color: '#DEDEDE', backgroundColor: '#181A1B' }, // 注册事件 on: { click: clickHandler } }, [ h('h1', 'Hello Snabbdom'), h('p', '这是段落') ]) function clickHandler () { // 此处的 this 指向对应的 vnode console.log(this.elm.innerHTML) }

40
life.caoover 1 year

Snabbdom 源码解析与如何调试(一)

如何学习源码 先宏观了解 带着目标看源码 看源码的过程要不求甚解 调试 参考资料 Snabbdom 的核心 使用 h() 函数创建 JavaScript 对象(VNode)描述真实 DOM init() 设置模块,创建 patch() patch() 比较新旧两个 VNode 把变化的内容更新到真实 DOM 树上 Snabbdom 源码 源码地址: https://github.com/snabbdom/snabbdom src 目录结构 ├── package │   ├── helpers │   │   └── attachto.ts 定义了 vnode.ts 中 AttachData 的数据结构 │   ├── modules │   │   ├── attributes.ts │   │   ├── class.ts │   │   ├── dataset.ts │   │   ├── eventlisteners.ts │   │   ├── hero.ts example 中使用到的自定义钩子 │   │   ├── module.ts 定义了模块中用到的钩子函数 │   │   ├── props.ts │   │   └── style.ts │   ├── h.ts h() 函数,用来创建 VNode │   ├── hooks.ts 所有钩子函数的定义 │   ├── htmldomapi.ts 对 DOM API 的包装 │   ├── init.ts 加载 modules、DOMAPI,返回 patch 函数 │   ├── is.ts 判断数组和原始值的函数 │   ├── jsx-global.ts jsx 的类型声明文件 │   ├── jsx.ts 处理 jsx │   ├── thunk.ts 优化处理,对复杂视图不可变值得优化 │   ├── tovnode.ts DOM 转换成 VNode │   ├── ts-transform-js-extension.cjs │   ├── tsconfig.json ts 的编译配置文件 │   └── vnode.ts 虚拟节点定义 h 函数 h() 函数介绍 在使用 Vue 的时候见过 h() 函数 new Vue({ router, store, render: h => h(App) }).$mount('#app') h() 函数最早见于 hyperscript,使用 JavaScript 创建超文本 Snabbdom 中的 h() 函数不是用来创建超文本,而是创建 VNode 函数重载 概念 参数个数或类型不同的函数 JavaScript 中没有重载的概念 TypeScript 中有重载,不过重载的实现还是通过代码调整参数 重载的示意 function add (a: number, b: number) { console.log(a + b) } function add (a: number, b: number, c: number) { console.log(a + b + c) } add(1, 2) add(1, 2, 3) function add (a: number, b: number) { console.log(a + b) } function add (a: number, b: string) { console.log(a + b) } add(1, 2) add(1, '2') 源码位置:src/package/h.ts // h 函数的重载 export function h (sel: string): VNode export function h (sel: string, data: VNodeData | null): VNode export function h (sel: string, children: VNodeChildren): VNode export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode export function h (sel: any, b?: any, c?: any): VNode { var data: VNodeData = {} var children: any var text: any var i: number // 处理参数,实现重载的机制 if (c !== undefined) { // 处理三个参数的情况 // sel、data、children/text if (b !== null) { data = b } if (is.array(c)) { children = c } else if (is.primitive(c)) { text = c } else if (c && c.sel) { children = [c] } } else if (b !== undefined && b !== null) { if (is.array(b)) { children = b } else if (is.primitive(b)) { // 如果 c 是字符串或者数字 text = b } else if (b && b.sel) { // 如果 b 是 VNode children = [b] } else { data = b } } if (children !== undefined) { // 处理 children 中的原始值(string/number) for (i = 0; i < children.length; ++i) { // 如果 child 是 string/number,创建文本节点 if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined) } } if ( sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' && (sel.length === 3 || sel[3] === '.' || sel[3] === '#') ) { // 如果是 svg,添加命名空间 addNS(data, children, sel) } // 返回 VNode return vnode(sel, data, children, text, undefined) }; VNode 一个 VNode 就是一个虚拟节点用来描述一个 DOM 元素,如果这个 VNode 有 children 就是 Virtual DOM 源码位置:src/package/vnode.ts export interface VNode { // 选择器 sel: string | undefined; // 节点数据:属性/样式/事件等 data: VNodeData | undefined; // 子节点,和 text 只能互斥 children: Array<VNode | string> | undefined; // 记录 vnode 对应的真实 DOM elm: Node | undefined; // 节点中的内容,和 children 只能互斥 text: string | undefined; // 优化用 key: Key | undefined; } export function vnode (sel: string | undefined, data: any | undefined, children: Array<VNode | string> | undefined, text: string | undefined, elm: Element | Text | undefined): VNode { const key = data === undefined ? undefined : data.key return { sel, data, children, text, elm, key } } snabbdom patch(oldVnode, newVnode) 打补丁,把新节点中变化的内容渲染到真实 DOM,最后返回新节点作为下一次处理的旧节点 对比新旧 VNode 是否相同节点(节点的 key 和 sel 相同) 如果不是相同节点,删除之前的内容,重新渲染 如果是相同节点,再判断新的 VNode 是否有 text,如果有并且和 oldVnode 的 text 不同,直接更新文本内容 如果新的 VNode 有 children,判断子节点是否有变化,判断子节点的过程使用的就是 diff 算法 diff 过程只进行同层级比较 image-20200102103653779 init 功能:init(modules, domApi),返回 patch() 函数(高阶函数) 为什么要使用高阶函数? 因为 patch() 函数再外部会调用多次,每次调用依赖一些参数,比如:modules/domApi/cbs 通过高阶函数让 init() 内部形成闭包,返回的 patch() 可以访问到 modules/domApi/cbs,而不需要重新创建 init() 在返回 patch() 之前,首先收集了所有模块中的钩子函数存储到 cbs 对象中 源码位置:src/package/init.ts const hooks: Array<keyof Module> = ['create', 'update', 'remove', 'destroy', 'pre', 'post'] export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) { let i: number let j: number const cbs: ModuleHooks = { create: [], update: [], remove: [], destroy: [], pre: [], post: [] } // 初始化 api const api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi // 把传入的所有模块的钩子方法,统一存储到 cbs 对象中 // 最终构建的 cbs 对象的形式 cbs = [ create: [fn1, fn2], update: [], ... ] for (i = 0; i < hooks.length; ++i) { // cbs['create'] = [] cbs[hooks[i]] = [] for (j = 0; j < modules.length; ++j) { // const hook = modules[0]['create'] const hook = modules[j][hooks[i]] if (hook !== undefined) { (cbs[hooks[i]] as any[]).push(hook) } } } …… return function patch (oldVnode: VNode | Element, vnode: VNode): VNode { …… } } patch 功能: 传入新旧 VNode,对比差异,把差异渲染到 DOM 返回新的 VNode,作为下一次 patch() 的 oldVnode 执行过程: 首先执行模块中的钩子函数 pre 如果 oldVnode 和 vnode 相同(key 和 sel 相同)调用 patchVnode(),找节点的差异并更新 DOM 如果 oldVnode 是 DOM 元素把 DOM 元素转换成 oldVnode 调用 createElm() 把 vnode 转换为真实 DOM,记录到 vnode.elm 把刚创建的 DOM 元素插入到 parent 中 移除老节点 触发用户设置的 create 钩子函数 源码位置:src/package/init.ts return function patch (oldVnode: VNode | Element, vnode: VNode): VNode { let i: number, elm: Node, parent: Node // 保存新插入节点的队列,为了触发钩子函数 const insertedVnodeQueue: VNodeQueue = [] // 执行模块的 pre 钩子函数 for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]() // 如果 oldVnode 不是 VNode,创建 VNode 并设置 elm if (!isVnode(oldVnode)) { // 把 DOM 元素转换成空的 VNode oldVnode = emptyNodeAt(oldVnode) } // 如果新旧节点是相同节点(key 和 sel 相同) if (sameVnode(oldVnode, vnode)) { // 找节点的差异并更新 DOM patchVnode(oldVnode, vnode, insertedVnodeQueue) } else { // 如果新旧节点不同,vnode 创建对应的 DOM // 获取当前的 DOM 元素 elm = oldVnode.elm! parent = api.parentNode(elm) as Node // 触发 init/create 钩子函数,创建 DOM createElm(vnode, insertedVnodeQueue) if (parent !== null) { // 如果父节点不为空,把 vnode 对应的 DOM 插入到文档中 api.insertBefore(parent, vnode.elm!, api.nextSibling(elm)) // 移除老节点 removeVnodes(parent, [oldVnode], 0, 0) } } // 执行用户设置的 insert 钩子函数 for (i = 0; i < insertedVnodeQueue.length; ++i) { insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]) } // 执行模块的 post 钩子函数 for (i = 0; i < cbs.post.length; ++i) cbs.post[i]() return vnode }

28
life.caoover 1 year

Snabbdom 源码解析与如何调试(二)

createElm 功能: createElm(vnode, insertedVnodeQueue),返回创建的 DOM 元素 创建 vnode 对应的 DOM 元素 执行过程: 首先触发用户设置的 init 钩子函数 如果选择器是!,创建评论节点 如果选择器为空,创建文本节点 如果选择器不为空解析选择器,设置标签的 id 和 class 属性 执行模块的 create 钩子函数 如果 vnode 有 children,创建子 vnode 对应的 DOM,追加到 DOM 树 如果 vnode 的 text 值是 string/number,创建文本节点并追击到 DOM 树 执行用户设置的 create 钩子函数 如果有用户设置的 insert 钩子函数,把 vnode 添加到队列中 源码位置:src/package/init.ts function createElm (vnode: VNode, insertedVnodeQueue: VNodeQueue): Node { let i: any let data = vnode.data if (data !== undefined) { // 执行用户设置的 init 钩子函数 const init = data.hook?.init if (isDef(init)) { init(vnode) data = vnode.data } } const children = vnode.children const sel = vnode.sel if (sel === '!') { // 如果选择器是!,创建注释节点 if (isUndef(vnode.text)) { vnode.text = '' } vnode.elm = api.createComment(vnode.text!) } else if (sel !== undefined) { // 如果选择器不为空 // 解析选择器 // Parse selector const hashIdx = sel.indexOf('#') const dotIdx = sel.indexOf('.', hashIdx) const hash = hashIdx > 0 ? hashIdx : sel.length const dot = dotIdx > 0 ? dotIdx : sel.length const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0, Math.min(hash, dot)) : sel const elm = vnode.elm = isDef(data) && isDef(i = data.ns) ? api.createElementNS(i, tag) : api.createElement(tag) if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot)) if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot + 1).replace(/\./g, ' ')) // 执行模块的 create 钩子函数 for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode) // 如果 vnode 中有子节点,创建子 vnode 对应的 DOM 元素并追加到 DOM 树上 if (is.array(children)) { for (i = 0; i < children.length; ++i) { const ch = children[i] if (ch != null) { api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue)) } } } else if (is.primitive(vnode.text)) { // 如果 vnode 的 text 值是 string/number,创建文本节点并追加到 DOM 树 api.appendChild(elm, api.createTextNode(vnode.text)) } const hook = vnode.data!.hook if (isDef(hook)) { // 执行用户传入的钩子 create hook.create?.(emptyNode, vnode) if (hook.insert) { // 把 vnode 添加到队列中,为后续执行 insert 钩子做准备 insertedVnodeQueue.push(vnode) } } } else { // 如果选择器为空,创建文本节点 vnode.elm = api.createTextNode(vnode.text!) } // 返回新创建的 DOM return vnode.elm } patchVnode 功能: patchVnode(oldVnode, vnode, insertedVnodeQueue) 对比 oldVnode 和 vnode 的差异,把差异渲染到 DOM 执行过程: 首先执行用户设置的 prepatch 钩子函数 执行 create 钩子函数首先执行模块的 create 钩子函数 然后执行用户设置的 create 钩子函数 如果 vnode.text 未定义如果 oldVnode.children 和 vnode.children 都有值调用 updateChildren() 使用 diff 算法对比子节点,更新子节点 如果 vnode.children 有值,oldVnode.children 无值清空 DOM 元素 调用 addVnodes(),批量添加子节点 如果 oldVnode.children 有值,vnode.children 无值调用 removeVnodes(),批量移除子节点 如果 oldVnode.text 有值清空 DOM 元素的内容 如果设置了 vnode.text 并且和和 oldVnode.text 不等如果老节点有子节点,全部移除 设置 DOM 元素的 textContent 为 vnode.text 最后执行用户设置的 postpatch 钩子函数 源码位置:src/package/init.ts function patchVnode (oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) { const hook = vnode.data?.hook // 首先执行用户设置的 prepatch 钩子函数 hook?.prepatch?.(oldVnode, vnode) const elm = vnode.elm = oldVnode.elm! const oldCh = oldVnode.children as VNode[] const ch = vnode.children as VNode[] // 如果新老 vnode 相同返回 if (oldVnode === vnode) return if (vnode.data !== undefined) { // 执行模块的 update 钩子函数 for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // 执行用户设置的 update 钩子函数 vnode.data.hook?.update?.(oldVnode, vnode) } // 如果 vnode.text 未定义 if (isUndef(vnode.text)) { // 如果新老节点都有 children if (isDef(oldCh) && isDef(ch)) { // 调用 updateChildren 对比子节点,更新子节点 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) } else if (isDef(ch)) { // 如果新节点有 children,老节点没有 children // 如果老节点有text,清空dom 元素的内容 if (isDef(oldVnode.text)) api.setTextContent(elm, '') // 批量添加子节点 addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { // 如果老节点有children,新节点没有children // 批量移除子节点 removeVnodes(elm, oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { // 如果老节点有 text,清空 DOM 元素 api.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { // 如果没有设置 vnode.text if (isDef(oldCh)) { // 如果老节点有 children,移除 removeVnodes(elm, oldCh, 0, oldCh.length - 1) } // 设置 DOM 元素的 textContent 为 vnode.text api.setTextContent(elm, vnode.text!) } // 最后执行用户设置的 postpatch 钩子函数 hook?.postpatch?.(oldVnode, vnode) } updateChildren 功能: diff 算法的核心,对比新旧节点的 children,更新 DOM 执行过程: 要对比两棵树的差异,我们可以取第一棵树的每一个节点依次和第二课树的每一个节点比较,但是这样的时间复杂度为 O(n^3) 在DOM 操作的时候我们很少很少会把一个父节点移动/更新到某一个子节点 因此只需要找同级别的子节点依次比较,然后再找下一级别的节点比较,这样算法的时间复杂度为 O(n) image-20200102103653779.png 在进行同级别节点比较的时候,首先会对新老节点数组的开始和结尾节点设置标记索引,遍历的过程中移动索引 在对开始和结束节点比较的时候,总共有四种情况oldStartVnode / newStartVnode (旧开始节点 / 新开始节点) oldEndVnode / newEndVnode (旧结束节点 / 新结束节点) oldStartVnode / oldEndVnode (旧开始节点 / 新结束节点) oldEndVnode / newStartVnode (旧结束节点 / 新开始节点) image-20200109184608649 开始节点和结束节点比较,这两种情况类似oldStartVnode / newStartVnode (旧开始节点 / 新开始节点) oldEndVnode / newEndVnode (旧结束节点 / 新结束节点) 如果 oldStartVnode 和 newStartVnode 是 sameVnode (key 和 sel 相同)调用 patchVnode() 对比和更新节点 把旧开始和新开始索引往后移动 oldStartIdx++ / oldEndIdx++ image-20200103121812840 oldStartVnode / newEndVnode (旧开始节点 / 新结束节点) 相同 - 调用 patchVnode() 对比和更新节点 把 oldStartVnode 对应的 DOM 元素,移动到右边 - 更新索引 image-20200103125428541 oldEndVnode / newStartVnode (旧结束节点 / 新开始节点) 相同调用 patchVnode() 对比和更新节点把 oldEndVnode 对应的 DOM 元素,移动到左边 更新索引 image-20200103125735048 如果不是以上四种情况遍历新节点,使用 newStartNode 的 key 在老节点数组中找相同节点 如果没有找到,说明 newStartNode 是新节点创建新节点对应的 DOM 元素,插入到 DOM 树中 如果找到了判断新节点和找到的老节点的 sel 选择器是否相同 如果不相同,说明节点被修改了重新创建对应的 DOM 元素,插入到 DOM 树中 如果相同,把 elmToMove 对应的 DOM 元素,移动到左边 image-20200109184822439 循环结束当老节点的所有子节点先遍历完 (oldStartIdx > oldEndIdx),循环结束 新节点的所有子节点先遍历完 (newStartIdx > newEndIdx),循环结束 如果老节点的数组先遍历完(oldStartIdx > oldEndIdx),说明新节点有剩余,把剩余节点批量插入到右边 image-20200103150918335 ​ 如果新节点的数组先遍历完(newStartIdx > newEndIdx),说明老节点有剩余,把剩余节点批量删除 image-20200109194751093 源码位置:src/package/init.ts function updateChildren (parentElm: Node, oldCh: VNode[], newCh: VNode[], insertedVnodeQueue: VNodeQueue) { let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx: KeyToIndexMap | undefined let idxInOld: number let elmToMove: VNode let before: any while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { // 索引变化后,可能会把节点设置为空 if (oldStartVnode == null) { // 节点为空移动索引 oldStartVnode = oldCh[++oldStartIdx] // Vnode might have been moved left } else if (oldEndVnode == null) { oldEndVnode = oldCh[--oldEndIdx] } else if (newStartVnode == null) { newStartVnode = newCh[++newStartIdx] } else if (newEndVnode == null) { newEndVnode = newCh[--newEndIdx] // 比较开始和结束节点的四种情况 } else if (sameVnode(oldStartVnode, newStartVnode)) { // 1. 比较老开始节点和新的开始节点 patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { // 2. 比较老结束节点和新的结束节点 patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right // 3. 比较老开始节点和新的结束节点 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldStartVnode.elm!, api.nextSibling(oldEndVnode.elm!)) oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left // 4. 比较老结束节点和新的开始节点 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { // 开始节点和结束节点都不相同 // 使用 newStartNode 的 key 再老节点数组中找相同节点 // 先设置记录 key 和 index 的对象 if (oldKeyToIdx === undefined) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) } // 遍历 newStartVnode, 从老的节点中找相同 key 的 oldVnode 的索引 idxInOld = oldKeyToIdx[newStartVnode.key as string] // 如果是新的vnode if (isUndef(idxInOld)) { // New element // 如果没找到,newStartNode 是新节点 // 创建元素插入 DOM 树 api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!) } else { // 如果找到相同 key 相同的老节点,记录到 elmToMove 遍历 elmToMove = oldCh[idxInOld] if (elmToMove.sel !== newStartVnode.sel) { // 如果新旧节点的选择器不同 // 创建新开始节点对应的 DOM 元素,插入到 DOM 树中 api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm!) } else { // 如果相同,patchVnode() // 把 elmToMove 对应的 DOM 元素,移动到左边 patchVnode(elmToMove, newStartVnode, insertedVnodeQueue) oldCh[idxInOld] = undefined as any api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!) } } // 重新给 newStartVnode 赋值,指向下一个新节点 newStartVnode = newCh[++newStartIdx] } } // 循环结束,老节点数组先遍历完成或者新节点数组先遍历完成 if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) { if (oldStartIdx > oldEndIdx) { // 如果老节点数组先遍历完成,说明有新的节点剩余 // 把剩余的新节点都插入到右边 before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else { // 如果新节点数组先遍历完成,说明老节点有剩余 // 批量删除老节点 removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) } } } 调试 updateChildren <ul> <li>首页</li> <li>微博</li> <li>视频</li> </ul> <ul> <li>首页</li> <li>视频</li> <li>微博</li> </ul> image-20200112120036948 Snabbdom 源码解析与如何调试介绍到此

52
life.caoover 1 year

吐血整理react快速入门精华(一)

1. React 介绍 React 是一个用于构建用户界面的 JavaScript 库,它只负责应用的视图层,帮助开发人员构建快速且交互式的 web 应用程序。 React 使用组件的方式构建用户界面。 2. JSX 语法 在 React 中使用 JSX 语法描述用户界面,它是一种 JavaScript 语法扩展。 在 React 代码执行之前,Babel 会将 JSX 语法转换为标准的 JavaScript API。 JSX 语法就是一种语法糖,让开发人员使用更加舒服的代码构建用户界面。 2.1 在 JSX 中使用表达式 const user = { firstName: 'Harper', lastName: 'Perez' } function formatName(user) { return user.firstName + ' ' + user.lastName; } const element = <h1>Hello, {formatName(user)}!</h1>; JSX 本身其实也是一种表达式,将它赋值给变量,当作参数传入,作为返回值都可以。 function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; } 2.2 属性 如果属性值为字符串类型,需要加引号,属性名称推荐采用驼峰式命名法。 const element = <div greeting="hello"></div>; 如果属性值为JavaScript表达式,属性值外面加大括号。 const element = <img src={user.avatarUrl} />; // 注意大括号外面不能加引号,JSX 会将引号当中的内容识别为字符串而不是表达式 2.3 JSX 单标记必须闭合 如果 JSX 是单标记,必须闭合,否则报错。 const element = <img src={user.avatarUrl} /> const element = <input type="text"/> 2.4 className 为 JSX 标记添加类名需要使用 className,而不是class。 const element = <img src={user.avatarUrl} className="rounded"/>; 2.5 JSX 自动展开数组 const ary = [<p>哈哈</p>, <p>呵呵</p>, <p>嘿嘿</p>]; const element = ( <div>{ary}</div> ); // 解析后 /* <div> <p>哈哈</p> <p>呵呵</p> <p>嘿嘿</p> </div> */ 2.6 三元运算 { boolean ? <div>Hello React</div> : null } { boolean && <div>Hello React</div> } 2.7 循环 const persons = [{ id: 1, name: '张三', age: 20 }, { id: 2, name: '李四', age: 15 }, { id: 3, name: '王五', age: 22 }] <ul> { persons.map(person => <li key={person.id}> {person.name} {person.age} </li>) } </ul> 2.8 事件 {/* 第一个参数即是事件对象 不需传递 */} <button onClick={this.eventHandler}>按钮</button> {/* 需要传递事件对象 */} <button onClick={e=>this.eventHandler('arg',e)}>按钮</button> {/* 最后一个参数即是事件对象 不需传递 */} <button onClick={this.eventHandler.bind(null, 'arg')}>按钮</button> constructor () { this.eventHandler = this.eventHandler.bind(this) } eventHandler () {} <button onClick={this.eventHandler}>按钮</button> 2.9 样式 2.9.1 行内样式 class App extends Component { render() { const style = {width: 200, height: 200, backgroundColor: 'red'}; return <div style={style}></div> } } 2.9.2 外链样式 // Button.js import styles from './Button.module.css'; class Button extends Component { render() { return <button className={styles.error}>Error Button</button>; } } 2.9.3 全局样式 import './styles.css' 2.10 ref 属性 2.10.1 createRef class Input extends Component { constructor() { super() this.inputRef = React.createRef() } render() { return ( <div> <input type="text" ref={this.inputRef} /> <button onClick={() => console.log(this.inputRef.current)}> button </button> </div> ) } } 2.10.2 函数参数 class Input extends Component { render() { return ( <div> <input type="text" ref={input => (this.input = input)} /> <button onClick={() => console.log(this.input)}>button</button> </div> ) } } 2.10.3 ref 字符串 不推荐使用,在严格模式下报错。 class Input extends Component { render() { return ( <div> <input type="text" ref="username" /> <button onClick={() => console.log(this.refs.username)}>button</button> </div> ) } } 2.10.4 获取组件实例 点击按钮让 input 文本框获取焦点。 input 文本框以及让文本框获取焦点的方法定义在 Input 组件中,在 App 组件中引入 Input 组件,按钮定义在 App 组件中。 // Input.js class Input extends Component { constructor() { super() this.inputRef = React.createRef() this.focusInput = this.focusInput.bind(this) } focusInput() { this.inputRef.current.focus() } render() { return ( <div> <input type="text" ref={this.inputRef} /> </div> ) } } // App.js class App extends Component { constructor() { super() this.InputComponentRef = React.createRef() } render() { return ( <div className="App"> <Input ref={this.InputComponentRef} /> <button onClick={() => this.InputComponentRef.current.focusInput()}>button</button> </div> ) } 3. 组件 3.1 什么是组件 React 是基于组件的方式进行用户界面开发的. 组件可以理解为对页面中某一块区域的封装。 3.2 创建组件 3.2.1 创建类组件 import React, { Component } from 'react'; class App extends Component { render () { return <div>Hello, 我是类组件</div> } } 3.2.2 创建函数组件 const Person = () => { return <div>Hello, 我是函数型组件</div>; } 注意事项 组件名称首字母必须大写,用以区分组件和普通标签。 jsx语法外层必须有一个根元素 3.3 组件 props 3.3.1 props 传递数据 在调用组件时可以向组件内部传递数据,在组件中可以通过 props 对象获取外部传递进来的数据。 <Person name="乔治" age="20"/> <Person name="玛丽" age="10"/> // 类组件 class Person extends Component { render() { return ( <div> <h3>姓名:{this.props.name}</h3> <h4>年龄:{this.props.age}</h4> </div> ); } } // 函数组件 const Person = props => { return ( <div> <h3>姓名:{props.name}</h3> <h4>年龄:{props.age}</h4> </div> ); } 注意: props 对象中存储的数据是只读的,不能在组件内部被修改。 当 props 数据源中的数据被修改后,组件中的接收到的 props 数据会被同步更新。( 数据驱动DOM ) 3.3.2 设置 props 默认值 class App extends Component { static defaultProps = {} } function ThemedButton(props) { } ThemedButton.defaultProps = { theme: "secondary", label: "Button Text" }; 3.3.3 组件 children 通过 props.children 属性可以获取到在调用组件时填充到组件标签内部的内容。 <Person>组件内部的内容</Person> const Person = (props) => { return ( <div>{props.children}</div> ); } 3.3.4 单向数据流 在React中, 关于数据流动有一条原则, 就是单向数据流动, 自顶向下, 从父组件到子组件. 单向数据流特性要求我们共享数据要放置在上层组件中. 子组件通过调用父组件传递过来的方法更改数据. 当数据发生更改时, React会重新渲染组件树. 单向数据流使组件之间的数据流动变得可预测. 使得定位程序错误变得简单.

390
life.caoover 1 year

吐血整理react快速入门精华(二)

3.4 类组件状态 state 3.4.1 定义组件状态 类组件除了能够从外部 (props) 接收状态数据以外还可以拥有自己的状态 (state),此状态在组件内部可以被更新,状态更新 DOM 更新。 组件内部的状态数据被存储在组件类中的 state 属性中,state 属性值为对象类型,属性名称固定不可更改。 class App extends Component { constructor () { super() this.state = { person: { name: '张三', age: 20 }, } } render () { return ( <div> {this.state.person.name} {this.state.person.age} </div> ); } } 3.4.2 更改组件状态 state 状态对象中的数据不可直接更改,如果直接更改 DOM 不会被更新,要更改 state 状态数据需要使用 setState方法。 class App extends Component { constructor () { this.state = { person: { name: '张三', age: 20 }, } this.changePerson = this.changePerson.bind(this) } changePerson () { this.setState({ person: { name: '李四', age: 15 } }) } render() { return ( <div> {this.state.person.name} {this.state.person.age} <button onClick={this.changePerson}>按钮</button> </div> ); } } 3.4.3 双向数据绑定 双向数据绑定是指,组件类中更新了状态,DOM 状态同步更新,DOM 更改了状态,组件类中同步更新。组件 <=> 视图。 要实现双向数据绑定需要用到表单元素和 state 状态对象。 class App extends Component { constructor () { this.state = { name: "张三" } this.nameChanged = this.nameChanged.bind(this) } nameChanged (event) { this.setState({name: event.target.value}); } render() { return ( <div> <div>{this.state.name}</div> <Person name={this.state.name} changed={this.nameChanged}/> </div> ) } } const Person = props => { return <input type="text" value={props.name} onChange={props.changed}/>; } 3.5 类组件生命周期函数 在组件完成更新之前需要做某种逻辑或者计算,就需要用到快照 componentDidUpdate(prevProps, prevState, snapshot) {} getSnapshotBeforeUpdate 方法会在组件完成更新之前执行,用于执行某种逻辑或计算,返回值可以在 componentDidUpdate 方法中的第三个参数中获取,就是说在组件更新之后可以拿到这个值再去做其他事情。 getSnapshotBeforeUpdate(prevProps, prevState) { return 'snapshot' } 3.6 Context 通过 Context 可以跨层级传递数据 // userContext.js import React from "react" const userContext = React.createContext("default value") const UserProvider = userContext.Provider const UserConsumer = userContext.Consumer export { UserProvider, UserConsumer } // App.js import { UserProvider } from "./userContext" class App extends Component { render() { return ( <UserProvider value="Hello React Context"> <A /> </UserProvider> ) } } // C.js import { UserConsumer } from "./userContext" export class C extends Component { render() { return ( <div> <UserConsumer> {username => { return <div>{username}</div> }} </UserConsumer> </div> ) } } context 的另一种用法 // userContext.js export default userContext // C.js import userContext from "./userContext" export class C extends Component { static contextType = userContext render() { return ( <div> {this.context} </div> ) } } 4. 表单 4.1 受控表单 表单控件中的值由组件的 state 对象来管理,state对象中存储的值和表单控件中的值时同步状态的 class App extends Component { constructor () { this.state = { username: "" } this.nameChanged = this.nameChanged.bind(this) } nameChanged (e) { this.setState({username: e.target.value}) } render() { return ( <form> <p>{this.state.username}</p> <input type="text" value={this.state.username} onChange={this.nameChanged}/> </form> ) } } 4.2 非受控表单 表单元素的值由 DOM 元素本身管理。 class App extends Component { constructor () { this.onSubmit = this.onSubmit.bind(this) } onSubmit(e) { console.log(this.username.value) e.preventDefault(); } render( <form onSubmit={this.onSubmit}> <input type="text" ref={username => this.username = username}/> </form> ) } 5. 路由 url地址与组件之间的对应关系,访问不同的url地址显示不同的组件。 下载:npm install react-router-dom 5.1.1 路由基本使用 // App.js import React from 'react'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom'; function Index() { return <div>首页</div>; } function News() { return <div>新闻</div>; } function App() { return ( <Router> <div> <Link to="/index">首页</Link> <Link to="/news">新闻</Link> </div> <div> <Route path="/index" component={Index}/> <Route path="/news" component={News}/> </div> </Router> ); } 5.1.2 路由嵌套 function News(props) { return ( <div> <div> <Link to={`${props.match.url}/company`}>公司新闻</Link> <Link to={`${props.match.url}/industry`}>行业新闻</Link> </div> <div> <Route path={`${props.match.path}/company`} component={CompanyNews} /> <Route path={`${props.match.path}/industry`} component={IndustryNews}/> </div> </div> ); } function CompanyNews() { return <div>公司新闻</div> } function IndustryNews() { return <div>行业新闻</div> } 5.1.3 路由传参 import url from 'url'; class News extends Component { constructor(props) { super(props); this.state = { list: [{ id: 1, title: '新闻1' }, { id: 2, title: '新闻2' }] } } render() { return ( <div> <div>新闻列表组件</div> <ul> this.state.list.map((item, index) => { return ( <li key={index}> <Link to={`/detail?id=${item.id}`}>{item.title}</Link> </li> ); }) </ul> </div> ); } } class Detail extends Component { constructor(props) { super(props); } const { query } = url.parse(this.props.location.search, true); console.log(query); // {id: 1} render() { return <div>新闻详情</div> } } 5.1.4 路由重定向 import { Redirect } from 'react-router-dom'; class Login extends Component { render() { if (this.state.isLogin) { return <Redirect to="/"/> } } }

354