robin

robin 查看完整档案

杭州编辑兰州工业学院  |  计算机网络技术 编辑SegmentFault  |  前端 编辑 blog.rnode.me 编辑
编辑

前端开发一枚,做过RN、小程序。

个人动态

robin 赞了文章 · 12月1日

create-react-app 4.0.0的新功能

Create React App是创建单页React应用(SPA)的推荐方式。它是React官方支持的,它提供了一个现代化的构建设置,无需配置。

有了它,你可以用一个命令来引导一个现代的React应用。由于Create React App同时支持 npmyarn,因此该命令可以根据您要使用的包管理器而略有不同。

要创建一个新的React应用程序,您可以运行以下任一命令:

npx create-react-app my-app
// OR
npm init react-app my-app
// OR
yarn create react-app my-app

此外,Create React App还省去了安装和配置Webpack和Babel等繁琐的任务,这些任务都是预先配置和隐藏的,所以你可以专注于编码。这意味着需要学习的东西更少,你只需要运行上面的任何一个命令就可以开始创建React应用。

虽然Create React App带有Webpack和Babel的预配置,但它不会强迫你使用自己的配置。你可以从Create React App中 eject ,并根据你的喜好添加你的自定义配置。

4.0版是主要发行版本,具有一些有趣的功能。

如果你已经在全局范围内安装了 create-react-app 你应该运行:

npm uninstall -g create-react-app

或者卸载它

yarn global remove create-react-app

再运行

npx create-react-app my-app

今后,确保 npx 始终使用最新版本。如果你的项目使用的是旧版本,你可以在下一节中看到迁移的说明。

迁移

当从3.4.x版本迁移到4.0.0版本时,在未被ejected的项目中运行以下内容

npm install --save --save-exact react-scripts@4.0.0

或者

yarn add --exact react-scripts@4.0.0

这应该会无缝更新react-scripts。如果你遇到任何错误,建议你删除你的 node_modules 文件夹,并通过运行重新安装你的依赖关系。

npm install or yarn install

这样就可以完成迁移,而不会出现任何错误。

新特性

Fast refresh 快速刷新

Fast refresh是旧功能(热重载)的官方React实现,这类似于热重载,但更可靠。

Fast refresh使您能够在运行中的应用程序中编辑React组件而不会丢失它们的状态,如下图所示。

简而言之,它使您可以实时调整React组件,这类似于 react-hot-loader

但是,与以前的版本相比,Fast refresh的主要优点之一是它可以热加载Hooks和保留状态。

Create React App 4.0.0使用react-refresh包和react refresh webpack plugin来实现这一点。

React 17(新的JSX transform)和TypeScript支持

Create React App 4.0.0提供对新的JSX转换和React 17的支持。新的JSX转换不会更改JSX语法,但是它为React带来了重大改进。

由于浏览器不能开箱即用地理解JSX, React开发人员依赖于编译器将他们的JSX 转换为浏览器能够理解的React函数调用,Babel和TypeScript等编译器主要用于此目的。

另外,诸如Create React App之类的工具包也附带了JSX转换,而4.0.0为使用TypeScript 4.1.0的新JSX转换提供了支持。

尽管升级到新的JSX转换是可选的,但它确实具有一些有趣的好处,例如:新的JSX 转换使您无需导入React即可使用JSX

如前所述,浏览器不理解JSX,因此必须将其编译为JavaScript函数,如果您有这样的React代码:

import React from 'react';

function App() {
  return <p>Hi I am Lawrence Eagles</p>;
}

旧的JSX 转换会将其转换为:

import React from 'react';

function App() {
  return `('p', null, 'Hi I am Lawrence Eagles');
}

问题是它调用了 React.createElement 函数,因此必须导入React。

另外,React.createElement 不支持一些性能优化技术,因此在JSX 转换的实现上还有改进的空间。

使用新的 JSX 转换,如果我们具有以下代码:

function App() {
  return <p>Hi I am Lawrence Eagles</p>;
}

它将转换为:

// 由编译器插入(请勿自己导入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('p', { children: 'Hi I am Lawrence Eagles });
}

注意我们的原始代码不需要导入React,它仍然可以正常工作。

另外,根据您的配置,新的JSX转换可以减小应用程序包的大小。

支持ESLint 7

React在后台使用ESLint来整理我们的代码。这很重要,因为React是一个JavaScript库,而JavaScript是松散类型的。这意味着在开发或编译时不会捕获错误,而在运行时会捕获错误。

诸如ESLint之类的Linters为您提供了一种在开发过程中对代码进行整理并捕获错误的机制。这可能是一个救命稻草,另外,有了linters,你可以编写自定义规则来强制执行你的编码风格。但是,如果要强制执行代码风格,建议您使用Prettier。

Create React App 4.0.0随附更新的 eslint-config-react-app,以支持新发布的ESLint 7。它还为 import/no-anonymous-default-exportJest 和React测试库提供了新规则。

创建React App,允许您扩展默认的ESLint规则,甚至替换它。但是建议不要替换它,因为这是导致讨厌的bug的已知原因。

在扩展ESLint基本规则之前,有一些需要注意的小问题,它们是:

  • 设置为 error 的任何规则都会破坏应用程序的构建过程
  • 当使用TypeScript时,你需要提供一个只针对TypeScript文件的 override 对象
  • 如前所述,请扩展并且不要替换ESLint规则

以下是扩展的ESLint规则的示例:

{
  "eslintConfig": {
    "extends": ["react-app", "shared-config"],
    "rules": {
      "additional-rule": "warn",
      "indent": ["error", 4]
    },
    "overrides": [
      {
        "files": ["**/*.ts?(x)"],
        "rules": {
          "additional-typescript-only-rule": "warn",
          "indent": ["error", "tab"]
        }
      }
    ]
  }
}

上面的规则仅执行以下操作:

  • 首先扩展react-app:"extends": ["react-app", "shared-config"]
  • 将任何其他规则设置为 “warn” 以避免停止构建过程:"additional-rule": "warn"
  • 将一致的缩进样式设置为4个空格:"indent": ["error", 4]
  • 如果使用TypeScript,请将TypeScript特定的配置放在 override 部分

    "overrides": [
      {
        "files": ["**/*.ts?(x)"],
        "rules": {
          "additional-typescript-only-rule": "warn",
          "indent": ["error", "tab"] // enforce tab indentation.
        }
      }
    ]

其他值得注意的变化

不再支持 typescript flagNODE_PATH

要将TypeScript添加到Create React App 4.0.0中,请执行以下操作:

npx create-react-app my-app --template typescript

代替这个:

npx create-react-app hello-tsx --typescript

另外,已经删除了对 NODE_PATH 的支持,因为通过在 jsconfig.json 中设置基本路径来代替了对 NODE_PATH 的支持。

  • 将Jest升级到26
  • 放弃了对Node 8的支持,Node 8在2019年年底达到报废期,不再支持。
  • 改用Workbox injectManifest插件,使 PWA 模板独立于自己的仓库。

总结

Create React App 4.0.0是一个重要的版本,它带来了一些很棒的功能。最让我兴奋的是fast refresh。不需要额外的包就能拥有这个功能,真的很不错。

新的JSX转换也是一个很好的补充,但是由于语法不变,因此并不能增强开发人员的体验。

其他更新也很酷,迁移过程是无缝的,这是一件很美好的事情。我们可以通过运行一个命令来获取所有这些更新。

最后,如果您需要有关此版本的更多详细信息,可以在这里找到。

https://github.com/facebook/c...

原文:https://blog.logrocket.com
作者:Lawrence Oputa

查看原文

赞 2 收藏 1 评论 0

robin 赞了回答 · 11月30日

解决js中的onclick不触发捕获过程?

onclick,检查下你的拼写

关注 2 回答 1

robin 赞了文章 · 11月29日

浏览器有几种储存机制?讲一讲:Storage for the Web

前言

今天我们来讲一讲 Web 存储有几种机制,并弄清楚什么是临时存储,什么是持久存储。

你可能不知道的是:我们平常口中所说的持久存储 localStorage 很多时候其实是系统级别的“临时存储”。

正文

IndexedDB

Indexed DB 的操作是异步的,不会阻塞主线程的执行,可以在 window、web workers、service workers 环境中使用。

IndexedDB 是基于文件存储的,API 较为复杂,包含 v1 v2 的差异,建议通过类库来使用,比如:Dexie.js

Cache Storage API

Cache Storage API 为缓存的 Request/Response 对象提供存储机制,常在 ServiceWorker 中应用。

异步,不会阻塞主线程的执行,可以在 window、web workers、service workers 环境中使用。

SessionStorage

同步,会阻塞主线程的执行。

一般用于储存临时性的少量的数据。

SessionStorage 是标签级别的,跟随者标签的生命周期,并且会随着标签的销毁而清空数据。

无法在 web workers、service workers 环境中使用。

它只能储存字符串,大小限制大约为 5MB。

LocalStorage

同步,会阻塞主线程的执行。

无法在 web workers、service workers 环境中使用。

它只能储存字符串,大小限制大约为 5MB。

Cookies

Cookies 有它的用途,但不适用于储存数据。

Cookie 会在每次 HTTP 请求的时候携带在请求头中,大体积的 Cookies 会显著增加 HTTP 请求的负担。

Cookies 读写是同步的,只能储存字符串,并且无法在 web workers 环境中使用。

File System API

File System API 和 FileWriter API 提供读取或写入文件到沙箱中(Sandboxed file system)。

它是异步的,不推荐使用,因为 File System API 只能在 Chromium 内核中使用。

File System Access API

File System Access API 设计用于便捷得读取和编辑本地文件。

但在读取或写入本地文件的时候,需要获得用户授权,并且授权状态无法持久化记录。

Application Cache

Application Cache 已被弃用,不建议使用。

建议迁移至 service workers 或 Cache API。

Storage 可以使用多少磁盘空间?

  • Chrome 允许使用 80% 的硬盘空间,单一的源(域名)可以使用 60% 的硬盘空间,可以通过 StorageManager API 检测最大的硬盘空间限额,其他基于 Chromium 内核的浏览器有不一样的限制,可能会允许使用更多的硬盘空间,查看更多实现 PR #3896
  • Internet Explorer 10(IE 10)及以上,最多可以储存 250MB,并在超过 10MB 的时候会提示用户
  • Firefox 允许使用 50% 的空闲硬盘空间,单个一级域名最多可以使用 2GB 硬盘空间,可以通过 StorageManager API 检测最大的硬盘空间限额
  • Safari 允许使用 1GB,当达到 1GB 的时候会提示用户(该数据可能不准确,没有找到 Safari 官方文档)

现代浏览器大多数已经不会再提示用户以授权更多的储存空间了。

如何检测储存空间是否可用?

在大多数浏览器中,可以通过 StorageManager API 检测储存空间总量与正在使用的量

if (navigator.storage && navigator.storage.estimate) {
  const quota = await navigator.storage.estimate();
  // quota.usage -> Number of bytes used.
  // quota.quota -> Maximum number of bytes available.
  const percentageUsed = (quota.usage / quota.quota) * 100;
  console.log(`You've used ${percentageUsed}% of the available storage.`);
  const remaining = quota.quota - quota.usage;
  console.log(`You can write up to ${remaining} more bytes.`);
}
// quota data
{
  "quota": 299977904946,
  "usage": 27154039,
  "usageDetails": {
    "caches": 26813093,
    "indexedDB": 305864,
    "serviceWorkerRegistrations": 35082
  }
}

注意:

  1. 并不是所有浏览器都实现了,因此使用之前需要先判断兼容性
  2. 需要捕获并处理超过配额限额的错误

IndexedDB 超限处理

indexedDB 超限将会执行 onabort 回调,并抛出一个 DOMException 错误,需要处理它的 QuotaExceededError 异常。

const transaction = idb.transaction(['entries'], 'readwrite');
transaction.onabort = function(event) {
  const error = event.target.error; // DOMException
  if (error.name == 'QuotaExceededError') {
    // Fallback code goes here
  }
};

Cache API 超限处理

抛出一个 Promise Rejection,QuotaExceededError 错误对象,需要处理它的 QuotaExceededError 异常。

try {
  const cache = await caches.open('my-cache');
  await cache.add(new Request('/sample1.jpg'));
} catch (err) {
  if (error.name === 'QuotaExceededError') {
    // Fallback code goes here
  }
}

浏览器什么时候回收存储空间?

Web Storage 分为两种储存模式,分别是:临时存储 Best Effort 和持久存储 Persistent。

默认情况下网站数据(包括 IndexedDB, Cache API, LocalStorage 等)都储存在临时存储 Best Effort 中,会在存储空间不足的时候被浏览器清除掉。

各个浏览器回收存储空间的差异:

  • Chrome 当浏览器存储空间不足时,会优先清除最近最少使用的数据,逐个清除,直至不再超限
  • IE 10+ 不会自动清除数据,但会阻止站点继续写入数据
  • Firefox 当磁盘空间充满时,会优先清除最近最少使用的数据,逐个清除,直至不再超限
  • Safari(iOS、iPadOS、MacOS) 会自动清除超过 7 天以上的数据,但不会清除“已添加至主屏幕”的网站和“PWA”网站

申请和查看持久存储 Persistent Storage

申请持久存储 Persistent Storage:

// Request persistent storage for site
if (navigator.storage && navigator.storage.persist) {
  const isPersisted = await navigator.storage.persist();
  console.log(`Persisted storage granted: ${isPersisted}`);
}

查看持久存储 Persistent Storage 授权状态:

// Check if site's storage has been marked as persistent
if (navigator.storage && navigator.storage.persist) {
  const isPersisted = await navigator.storage.persisted();
  console.log(`Persisted storage granted: ${isPersisted}`);
}

各个浏览器申请持久存储 Persistent Storage 的差异:

  • 在 Chrome 55 以后,申请持久存储只需要满足以下任一条件,即可自动获得持久存储权限,无需用户确认:

    • 该站点已添加书签, 并且用户的书签数小于等于5个
    • 站点有很高的"site engagement",通过这个命令可以查看: chrome://site-engagement/
    • 站点已添加到主屏幕
    • 站点启用了push通知功能
  • 在 Firefox 中,会提示用户授权

最后测试并验证:

  1. 打开 https://baidu.com,打开控制台输入 await navigator.storage.persist(),返回 true
  2. 打开 https://wy.guahao.com,打开控制台输入 await navigator.storage.persist(),返回 false

参考文献

查看原文

赞 21 收藏 15 评论 0

robin 赞了文章 · 11月29日

15 种微服务架构框架

这几年来,微服务这个概念越来越火了,火到什么程度呢?2019年有一个统计说,两千家企业里,45%在使用微服务,16%在实验开发和测试微服务架构,24%在学习微服务准备转型,只有剩下的15%的企业没有使用微服务。

微服务到底有什么好呢?微服务在2013年才被提出,短短几年就有这么快速的发展。微服务架构能够实现由小型自主服务组成一个整体应用,各个组成部分之间是松耦合的,复杂性低,各个部分可以独立部署,修复bug或者引入新特性更容易,能够独立扩展,不同技术栈之间可以使用不同框架、不同版本库甚至不同的操作系统平台。

对于中大型架构系统来说,微服务更加便捷,微服务成为很多企业架构重构的方向,同时也对架构师提出更高的挑战。目前有很多常用于微服务构建的框架,对于构建微服务架构能够带来一些帮助。

Java语言相关微服务框架

Spring Boot

Spring Boot的设计目的是简化新Spring应用初始搭建以及开发过程,2017年有64.4%的受访者决定使用Spring Boot,可以说是最受欢迎的微服务开发框架。利用Spring Boot开发的便捷度简化分布式系统基础设施的开发,比如像配置中心、注册、负载均衡等方面都可以做到一键启动和一键部署。

Spring Cloud

Spring Cloud是一个系列框架的合计,基于HTTP(s)的RETS服务构建服务体系,Spring Cloud能够帮助架构师构建一整套完整的微服务架构技术生态链。

Dubbo

Dubbo是由阿里巴巴开源的分布式服务化治理框架,通过RPC请求方式访问。Dubbo是在阿里巴巴的电商平台中逐渐探索演进所形成的,经历过复杂业务的高并发挑战,比Spring Cloud的开源时间还要早。目前阿里、京东、当当、携程、去哪等一些企业都在使用Dubbo。

Dropwizard

Dropwizard将Java生态系统中各个问题域里最好的组建集成于一身,能够快速打造一个Rest风格的后台,还可以整合Dropwizard核心以外的项目。国内现在使用Dropwizard还很少,资源也不多,但是与Spring Boot相比,Dropwizard在轻量化上更有优势,同时如果用过Spring,那么基本也会使用Spring Boot。

Akka

Akka是一个用Scala编写的库,可以用在有简化编写容错、高可伸缩性的Java和Scala的Actor模型,使用Akka能够实现微服务集群。

Vert.x/Lagom/ReactiveX/Spring 5

这四种框架主要用于响应式微服务开发,响应式本身和微服务没有关系,更多用于提升性能上,但是可以和微服务相结合,也可以提升性能。

.Net相关微服务框架

.NET Core

.NET Core是专门针对模块化微服务架构设计的,是跨平台应用程序开发框架,是微软开发的第一个官方版本。

Service Fabric

Service Fabric是微软开发的一个微服务框架,基于Service Fabric构建的很多云服务被用在了Azure上。

Surging

Surging是基于RPC协议的分布式微服务技术框架,基于.NET Core而来。

Microdot Framework

Microdot Framework用于编写定义服务逻辑代码,不需要解决开发分布式系统的挑战,能够很方便的进行MicrosoftOrleans集成。

Node.js相关微服务框架

Seneca

Seneca是Node.js的微服务框架开发工具,可以用于编写可用于产品环境的代码。

Hapi/Restify/LoopBack

这三种框架的分工不同,前两种更适合开发简单的微服务后端系统,第三种更适合用在大型复杂应用开发,还可以用在现有微服务上的构建。

Go相关微服务框架

Go-Kit/Goa/Dubbogo

Go-Kit是分布式开发的工具合集,适合用于大型业务场景下构建微服务;Goa是用Go语言构建的微服务框架;Dubbogo是和阿里巴巴开源的Dubbo能够兼容的Golang微服务框架。

Python相关微服务框架

Python相关的微服务框架非常少,用的比较多的是Nameko。Nameko让实现微服务变得更简单,同时也提供了很丰富的功能,比如支持负载均衡、服务发现还支持依赖自动注入等,使用起来很方便,但是有限速、超时和权限机制不完善等缺点。

总结

微服务已经成为很多大型互联网公司的选择,对于架构师和想要成为架构师的工程师来说,掌握微服务不仅要学会使用相关框架来实现,还要掌握具体用法,在具体的实践中仍然要避开很多坑。

原文链接:http://suo.im/66bi0x

image

查看原文

赞 7 收藏 5 评论 0

robin 回答了问题 · 11月29日

vue vuedraggable start end方法不生效

字面意思未能读取到id,你可以尝试设置 id 试试

关注 3 回答 1

robin 赞了文章 · 11月29日

React-Router动态路由设计最佳实践

写在前面

随着单页应用(SPA)概念的日趋火热,React框架在设计和实践中同样也围绕着SPA的概念来打造自己的技术栈体系,其中路由模块便是非常重要的一个组成部分。它承载着应用功能分区,复杂模块组织,数据传递,应用状态维护等诸多功能,如何结合好React框架的技术栈特性来进行路由模块设计就显得尤为重要,本文则以探索React动态路由设计最佳实践作为切入点,分享下在实际项目开发中的心得与体会。

为什么需要做动态路由

动态路由:对于大型应用来说,一个首当其冲的问题就是所需加载的 JavaScript 的大小。程序应当只加载当前渲染页所需的 JavaScript。有些开发者将这种方式称之为 "代码分拆(code-splitting)" — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。

1. 首屏加载效率

随着项目的业务需求持续添加,react中的代码复杂度将面临着持续上升的问题,同时由于react中的jsx和es6语法的文件在实际生产环境中,也会被babel-js重新编译成浏览器所支持的基于ES5的语法模块,各个模块打体积将会变得非常的臃肿不堪,直接影响到页面加载的等待时常。以下图为例,如果不做处理,我们的业务模块通常体积会达到兆级,这对首屏加载速率和用户体验的影响无疑是巨大的。

all_chunk

2. 降低模块间的功能影响

react中的jsx无疑是一个很方便的设计,能让开发者像写html一样来书写虚拟dom,但是它同样也贯彻执行着"all in js"的理念,最终构建完成后所有的业务代码都将打包到1-2个bundle文件中,这就等于将所有的功能模块都集中到了一个物理文件中,如果遇到业务处理的复杂性,接口层变更,异常处理出错等诸多代码健壮性问题时,一个子模块出现了错误,就很有可能导致用户界面整体性出错从而无法使用的风险。此外,如果业务模块需要分功能上线的时候,降低彼此之间的影响也是必须要考虑的。

3. 符合二八定律

通常在一个应用中,最重要和高频访的功能模块只占其中一小部分,约20%,其余80%尽管是多数,却是次要的。以后台系统为例,普通业务人员通常使用的高频模块只有3-5个,但是业务系统通常会有各式各样的权限设计,不同的权限映射着能访问的路由模块也不尽相同,虽然我们可以在用户的数据访问和路由地址上做拦截限制,但是同样也需要对其能访问的模块资源进行限制,才能做到真正的按需加载,随取随用。

4. 工具体系支撑

无论是react-router还是对应搭配的构建工具webpack,其中都有针对动态路由部分的设计与优化,使用好了往往能起到事半功倍的效果。

chunk_split2

简化版实现:bundle-loader

bundle-loader是webpack官方出品与维护的一个loader,主要用来处理异步模块的加载,将简单的页面模块转成异步模块,非常方便。

1. 改造前页面

import React from 'react'
import {Route, Router} from 'react-router-dom'
import createHistory from 'history/createHashHistory'
import './app.less'

import ReactChildrenMap from './containers/Commons/ReactChildrenMap'
import Home from './containers/Home/Home'
import Search from './containers/Search/Search'
import BookList from './containers/BookList/BookList'
import BookDetail from './containers/BookDetail/bookDetail.bundle.js'

const history = createHistory()

export default class App extends React.Component {
  render() {
    return (
      <Router history={history}>
        <Route render={({location}) => {
          return (
            <ReactChildrenMap key={location.pathname}>
              <Route location={location} exact path="/" component={Home}/>
              <Route location={location} path="/search" component={Search}/>
              <Route location={location} path="/detail" component={BookDetail}/>
              <Route location={location} path="/bookList/:bookId" component={BookList}/>
            </ReactChildrenMap>
          )
        }}/>
      </Router>
    );
  }
}

2. 在webpack.config.js中增加rules

// npm install bundle-loader -D
// 如果不想通过配置调用,也可以写成: import file from "bundle-loader?lazy&name=my-chunk!./file.js"的内嵌写法

module.exports = {
  module: {
    rules: [
      {
        test: /\.bundle\.js$/, // 通过文件名后缀自动处理需要转成bundle的文件
        include: /src/,
        exclude: /node_modules/,
        use: [{
          loader: 'bundle-loader',
          options: {
            name: 'app-[name]',
            lazy: true
          }
        }, {
          loader: 'babel-loader',
        }]
      }
    ]
  }
}

3. 在工程中使用带 xxx.bunlde.js结尾的类型文件时,就会被bundle-loader识别并做编译处理

// bundle-loader处理前
import BookDetail from './containers/BookDetail/bookDetail.bundle.js'

// bundle-loader处理后
module.exports = function(cb) {
  // 自动会被bundle-loader处理成异步加载的写法
  require.ensure([], function(require) {
    cb(require("!!../../../node_modules/babel-loader/lib/index.js!./bookDetail.bundle.js"));
  }, "app-bookDetail.bundle");
}
// WEBPACK FOOTER //
// ./containers/BookDetail/bookDetail.bundle.js

4. 创建LazyBundle.js文件,这个文件会用来调用被bundle-loader处理后的组件

// LazyBundle.js
import React, { Component } from 'react'

export default class LazyBundle extends React.Component {

  state = {
    // short for "module" but that's a keyword in js, so "mod"
    mod: null
  }

  componentWillMount() {
    this.load(this.props)
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.load !== this.props.load) {
      this.load(nextProps)
    }
  }

  load(props) {
    this.setState({
      mod: null
    })
    
    props.load((mod) => {
      this.setState({
        // handle both es imports and cjs
        mod: mod.default ? mod.default : mod
      })
    })
  }

  render() {
    if (!this.state.mod) {
      return false
    }
    return this.props.children(this.state.mod)
  }
}

5. 对我们需要异步加载的组件函数进行二次封装

注:react-router3和4由于是不兼容升级,所以处理动态路由的方法也略有不同,在此列出了两种版本下的处理方式可供参考

import LazyBundle from './LazyBundle'
import BookDetail from './containers/BookDetail/bookDetail.bundle.js'

/* use for react-router4
 * component={lazyLoadComponent(BookDetail)}
 */
const lazyLoadComponent = (comp) => (props) => (
  <LazyBundle load={comp}>
    {(Container) => <Container {...props}/>}
  </LazyBundle>
)

/* use for react-router3
 * getComponent={lazyLoadComponentOld(BookDetail)}
 */
function lazyLoadComponentOld(comp) {
  return (location, cb) => {
    comp(module => cb(null, module.default));
  }
}

6. 改造后页面

import React from 'react'
import {Route, Router} from 'react-router-dom'
import createHistory from 'history/createHashHistory'

const history = createHistory()

import './app.less'

import Home from 'containers/Home/Home'
import ReactChildrenMap from './containers/Commons/ReactChildrenMap'
import Search from './containers/Search/Search'
import BookList from './containers/BookList/BookList'
import LazyBundle from './LazyBundle'
import BookDetail from './containers/BookDetail/bookDetail.bundle.js'

/* use for react-router4
 * component={lazyLoadComponent(BookDetail)}
 */
const lazyLoadComponent = (comp) => (props) => (
  <LazyBundle load={comp}>
    {(Container) => <Container {...props}/>}
  </LazyBundle>
)

export default class App extends React.Component {
  render() {
    return (
      <Router history={history}>
        <Route render={({location}) => {
          return (
            <ReactChildrenMap key={location.pathname}>
              <Route location={location} exact path="/" component={Home}/>
              <Route location={location} path="/search" component={Search}/>
              <Route location={location} path="/detail" component={lazyLoadComponent(BookDetail)} />
              <Route location={location} path="/bookList/:bookId" component={BookList}/>
            </ReactChildrenMap>
          )
        }}/>
      </Router>
    );
  }
}

完成构建后我们就可以从浏览器中看到,我们定制后的模块已经被能被支持异步加载了
bundle_chunk

同时在webpack构建中也能清晰地看到多了一个chunk:

bundle_name

高阶版实现:dynamic-imports

dynamic-imports是webpack在升级到2版本以后,对js的模块处理进行了增强的,其中就有对require.ensure的改进,基于原生的Promise对象进行了重新实现,采用了import()作为资源加载方法,将其看做一个分割点并将其请求的module打包为一个独立的chunk。import()以模块名称作为参数并且返回一个Promise对象,具体介绍可以参考笔者之前写过的翻译文章Webpack2 升级指南和特性摘要,具体使用比对如下:

// require.ensure
module.exports = function (cb) {
  require.ensure([], function(require) {
    var app = require('./file.js');
    cb(app);
  }, "custom-chunk-name");
};

// import()
import("./module").then(module => {
    return module.default;
}).catch(err => {
    console.log("Chunk loading failed");
});
// This creates a separate chunk for each possible route
​````

结合import的高级特性,我们就可以省去bundle-loader的处理方式,直接在原生模块上进行动态路由处理,具体设计实现如下:

1.封装一个高阶组件,用来实现将普通的组件转换成动态组件

import React from 'react'

const AsyncComponent = loadComponent => (
  class AsyncComponent extends React.Component {
    state = {
      Component: null,
    }

    componentWillMount() {
      if (this.hasLoadedComponent()) {
        return;
      }

      loadComponent()
        .then(module => module.default)
        .then((Component) => {
          this.setState({Component});
        })
        .catch((err) => {
          console.error(`Cannot load component in <AsyncComponent />`);
          throw err;
        });
    }

    hasLoadedComponent() {
      return this.state.Component !== null;
    }

    render() {
      const {Component} = this.state;
      return (Component) ? <Component {...this.props} /> : null;
    }
  }
);

export default AsyncComponent;

2.对我们需要用到的普通组件进行引入和包装处理

// 组件增强
const Search = AsyncComponent(() => import("./containers/Search/Search"))

// 路由调用
<Route location={location} path="/list" component={BookList} />

利用weback3中的Magic Comments对生成的chunk指定chunkName

const BookList = AsyncComponent(() => 
  import(/* webpackChunkName: "bookList" */ "./containers/BookList/BookList")
)

完成构建后我们就可以从浏览器中看到,我们定制后的模块也和之前一样,被能被支持异步加载了
async_component

同时在webpack构建界面中的能看到多了一个chunk,并且chunkName就是我们自定义的名称,对于定位分析一些模块问题时会非常管用。
bundle_name_comment

从中我们也不难发现,相对于bundle-loader,dynamic-imports + AsyncComponent高阶组件的方式更为简单灵活,同时对于现有的代码改动也较小,故作为在实际开发中的首选方案使用,同时我们也推荐一个非常不错的webpack的chunk分析工具webpack-bundle-analyzer,方便查看每个异步路由中的构建的具体模块内容。

One more thing:路由模块的组织

react-router功能强大,上手简单,作为官方唯一指定的路由框架已经成为了react应用开发中必备的部分,但是由于react天生组件化的原因,意味着react-router的配置文件中在实际使用中,会难免出现如下不佳场景:

1、路由配置入口文件持续臃肿,文件越引越多

components

2、路由配置会随着业务嵌套越来越深,团队协作开发时极易产生冲突

route-config

3、非jsx写法,模块清晰简单,但是会导致路由模块和业务模块耦合,不利于集中管理,同时无法明确表达出母子路由的嵌套关系,参见huge-apps

js-route

问题来了:如何既保证路由模块的清晰简单,又能集中管理维护,还能支持嵌套定义和动态加载?

借鉴python flask中的blueprint设计思路,重新实现路由模块的划分

经过前面的分析,我们不难发现react-router的路由配置模块会随着业务的深入变得越来越臃肿,其根本原因在于我们将所有的资源和配置信息都写在了一个文件中,这和软件设计中提倡的清晰单一,低耦合高内聚等指导原则是背道而驰的,为此我们针对路由模块的划分这块进行了重构,改进方式如下:

1.拆分routes.js入口文件

将路由模块的整体由一个routes.js文件拆成若干个彼此间互相独立的子路由模块文件模块的拆分原则可以和业务功能划分一一对应,逐步减少主配置中的内容耦合。

routes
├── asyncComponent.js
├── callManage.js
├── index.js
├── opportunity.js
├── osManage.js
├── salesKit.js
├── salesManage.js
├── system.js
├── uploadOppor.js
└── workBoard.js

2.在模块的入口文件index.js中完成对各个子模块的引入,如下所示:

import React from 'react';
import { Route, IndexRedirect } from 'react-router';
import NotFound from '../components/NotFound';
import Layout from '../containers/Main';
import Opportunity from './opportunity';
import OsManage from './osManage';
import SalesKit from './salesKit';
import System from './system';
import CallManage from './callManage';
import SalesManage from './salesManage';
import WorkBoard from './workBoard';
import UploadOppor from './uploadOppor';

const routeList = [
  Opportunity,
  UploadOppor,
  OsManage,
  SalesKit,
  System,
  CallManage,
  SalesManage,
  WorkBoard
];

export default (
  <Route path='/' component={Layout} >
    {routeList}
    <Route path='*' component={NotFound} />
  </Route>
);

3.在子路由模块中完成对应具体业务模块的加载,支持同时混合使用同步和异步组件的管理方式

import React from 'react';
import { Route } from 'react-router';
import UploadOpportunities from '../containers/opportunity/UploadOpportunities'
import UploadVisitOpportunity from '../containers/UploadVisitOpportunity'
import asyncComponent from './asyncComponent'

// upload_frozen_phone
const UploadFrozenPhone = asyncComponent(
  () => import(/* webpackChunkName: "upload_frozen_phone" */'../components/uploadFrozenPhone/UploadFrozenPhone')
);

// upload_phone_state
const UploadPhoneState = asyncComponent(
  () => import(/* webpackChunkName: "upload_phone_state" */'../components/uploadPhoneState/UploadPhoneState')
);

export default (
  <Route key='uploadOpportunities'>
    <Route path='upload_opportunity/:type' component={UploadOpportunities} />
    <Route path='upload_visit_opportunity' component={UploadVisitOpportunity} />
    <Route path='frozen_phone' component={UploadFrozenPhone} />
    <Route path='phone_state' component={UploadPhoneState} />
  </Route>
);

4. 优势小结:

这样重构的好处是即使未来随着业务的深入,对应的开发人员也只需要维护自身负责的子路由模块,再在根路由下进行注册即可使用,并且由于子路由模块都从物理文件上进行了隔离,也能最大程度地减少协作冲突,同时,因为维持了jsx的描述型结构,路由的嵌套关系和集中维护等优点依旧能沿用。

总结

本文从react-router的动态路由实践着手,整合了webpack的bundle-loader,dynamic-imports和高阶组件等实践的明细介绍,附带介绍了改进路由模块的组织方式,以此作为react-router深入实践的经验总结,希望能对各位读者在实际项目开发中有所帮助。

参考文献

查看原文

赞 60 收藏 94 评论 11

robin 回答了问题 · 11月29日

解决vue达到什么水平才能封装组件库?

封装不一定非要必须达到element和bootstrap-vue这种级别,一口气吃不成胖子,可以一点一点进行,先实现个简单的,能满足当前项目的需求,然后不断完善,优秀的组件库也是一点点积累起来的,并非一朝一夕。

关注 3 回答 2

robin 赞了文章 · 11月28日

2020 中国技术先锋年度评选的初心和起源

转眼间,腊月降至。
我和合伙人 @高阳Sunny 辗转多地出差后,在杭州落脚。
杭州是 SegmentFault 的发源地,我们回到这里回望总结,也思考要做一个怎样的年度盘点回馈开发者。


缘起:SegmentFault 年度榜单

SegmentFault 年度榜单起源于 2015 年,从 2015 年起我们每年都会根据社区用户行为(如文章 & 问答发布数量、获得声望 & 点赞量等)的综合分析,从「技术问答」和「专栏文章」两个维度进行社区活跃技术作者的评选。他们热衷于分享知识与经验,他们布道技术未来,他们让众多开发者受益,他们是SegmentFault TopWriter

2019 年(去年),我们在 TopWriter 评选的基础上首创中国技术品牌影响力企业最受开发者欢迎的技术活动,不同于其他媒体轰轰烈烈的年度评奖,SegmentFault 的榜单评选依据于社区用户行为的分析和反馈。

当开发者生态和技术品牌受到越来越多企业的重视,我们希望让开发者看到那些真正坚持长期价值,积极输出优质技术内容、坚持产品创新为开发者创造价值和便利的企业或团队,帮助他们获得更多关注,也推动更多企业加大在开发者生态的投入,造福开发者。

我们也结合 SegmentFault 技术活动板块 的访问数据,真诚地向广大开发者推介最受 SegmentFault 开发者用户欢迎的技术活动。他们邀请顶尖讲师布道、和开发者密切互动,我们相信这些优质的内容能够为开发者成长助力,也希望在活动传播上给予这些辛苦的活动主办方以支持。


2020:寻找技术先锋

2020 年 8 月初,我们在内部重新启动了榜单评选。

为了更好地进行榜单传播,我们将现有的几个榜单品牌提炼升级 —— 在我们看来,无论是积极输出优质 UGC 内容的技术作者,还是高质量技术活动的主办方在开发者领域积极投入的科技企业,都是推动社会创新的 “先锋力量”,由此 “中国技术先锋”的榜单品牌正式诞生。

image

大家都说 2020 年很魔幻,但是我们也欣喜地看到了很多正向改变 ——

突如其来的疫情让全人类经历了一次“数字化生存”大考,政企上云、传统行业数字化转型在大环境中被催化。作为新基建的底层支撑,芯片、服务器、操作系统、中间件、数据库等一系列信创技术,在全国范围内被广泛关注。

日新月异的技术革命,数字经济的新一轮爆发,背后是无数开发者和科技企业夜以继日的付出。他们面对不断变化的外部环境,扎根行业,他们信奉技术力量敢于技术创新践行技术信仰

他们是技术先锋,他(它)们的每一点创新都值得被更多人看见。(点击申报榜单)


全新亮相:先锋 30 人之开源人物

基于历史的 3 个榜单,在今年我们增设了 “技术先锋 30 人” 的评选。未来每年我们都将聚焦在某一具体领域,寻找最值得关注的技术先锋,推介给大家。

而今年,基于 SegmentFault 团队在全年对于 “开源” 领域的重点关注和观察,我们将技术先锋的评选聚焦在开源领域。我们也邀请到了国内专业的开源联盟 开源社 (点击此处了解开源社)来和我们联合主办本次评选, —— “中国开源先锋 30 人”全新亮相!

image

而本次评选之所以聚焦于 “人” 而非项目、产品、公司,是因为开源项目/开源商业都只是开源生态中的一环,而 “人” 才是万事万物的本源和基础。(这也是为什么 SegmentFault 2015 年起的第一个榜单就是基于人的 “TopWriter” 的评选。)

所谓先锋,我们理解不仅限于开发者,贡献代码的开发者、开源项目发起人、开源布道师、开源治理的先锋人物、关注开源的投资人等对推动开源生态发展有帮助的人都是榜单的目标对象。

开源项目开源治理开源布道开源商业开源教育等都是我们重点关注的领域。

不投票拉票,不要求转发叫嚣;
支持自主申报,未报名的合适人选也有可能上榜;
开源社执行长王伟老师主持的《2020 中国开源年度报告》将作为本次评选的部分客观数据支撑,同时我们也将综合评审团的主观推荐;
分不同维度推介,不设排名,都是 “心选”,更加多元;

这不是一个千篇一律的人物排行,而是 ——
面向开发者的 “米其林推荐”(援引自开源社理事长庄表伟老师的提炼)
“心(舌)尖上的开源人物”(援引自开源社创始人刘天栋.Ted 和开源社理事李思颖的趣谈)

榜单发布时除发布名单,每位人物还都会配有推介理由,它既是对于 2020 年度的总结也是由 SegmentFault 思否和开源社为你联合推介的 “2021 年最值得关注的开源人物”

image
2020中国开源年会暨阿帕奇中国路演大会周边,Designed by 开源社理事长庄表伟 & SegmentFault 思否设计师楚云

这不是一份面向所有人的榜单,而是特别面向关注开源领域朋友们的信息参考。
我们不依赖营销手段传播榜单,而是通过内容和价值驱动。
或许不一定榜单的每个领域都对你有所启发,但我们相信这份榜单中总有几位你不曾重点关注却能为你创造价值、带来启发的人,这便是这份榜单最大的价值。

我们期待有更多可能并不那么 “广为人知” 的开源人物也可以通过这份 “米其林推荐” 被更多人认识。除了开源项目、开源商业以外,教育、开源治理等相对冷门的领域也有所发声。

在此我特别感谢开源社创始人刘天栋.Ted,理事长庄表伟,执行长王伟,和理事李思颖几位老师的大力支持,他们的很多建议是这次评选能够顺利推动的基石。我们的一拍即合,也让我更加坚定了这次评选是一个能够为开发者创造价值的、与众不同的活动。

后记

希望我的本篇文章可以帮助您更好地了解 2020 中国技术先锋年度评选的 “前世今生”,希望它较为全面地让您理解 SegmentFault 思否做这件事的 WHY 和 HOW和年底蜂拥而至的榜单有何不同

本篇文章更多篇幅用于介绍 2019-2020 近两年新推出的几个榜单,是因为 2015 年至今 TopWriter 的评选已经为大家所熟悉,评选机制也更加成熟,无需自主申报,全部依据于 SegmentFault 后台数据分析。For Developers,By Developers. 社区开发者是 SegmentFault 发展的基石

最后,如果我们理念相合,我真诚地邀请您报名参与本次评选,我们将尽最大努力,让您的举手之劳为开发者、为生态建设、为关注开源领域的人们带来更大价值。

个人和团队的一些观点,如果有更好的建议,也欢迎开发者和行业前辈多指点,我们虚心学习、持续优化、共同进步。

榜单报名

中国技术品牌影响力企业、最受开发者欢迎的技术活动评选:2020 中国技术先锋年度评选
中国开源先锋 30 人之心尖上的开源人物(SegmentFault 思否和开源社联合主办):2020 中国开源先锋 30 人 之 心尖上的开源人物


相关阅读

查看原文

赞 18 收藏 0 评论 0

robin 关注了用户 · 11月28日

波波Nadia @bobonadia

SegmentFault COO & 思否编程联席 CEO
思否技术人访谈专栏作者,寻求报道、个人/厂商投稿、提供线索: nadia@sifou.com

关注 84

认证与成就

  • 获得 59 次点赞
  • 获得 12 枚徽章 获得 0 枚金徽章, 获得 2 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2017-07-09
个人主页被 1.3k 人浏览