React 从 v15 升级到 v16 后,为什么要重构底层架构

React 从 v15 升级到 v16 后重构了整个架构,v16 及以上版本一直沿用新架构,重构的主要原因在于:旧架构无法实现 Time Slice。

01

新旧架构介绍

React15 架构可以分为两部分:

  • Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。
  • Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。

在 Reconciler 中,mount 的组件会调用 mountComponent,update 的组件会调用updateComponent,这两个方法都会递归更新子组件,更新流程一旦开始,中途无法中断。

基于这个原因,React16 重构了架构。重构后的架构一直沿用至今,可以分为 3 部分:

  • Scheduler(调度器)——调度任务的优先级,高优先级任务优先进入 Reconciler。
  • Reconciler(协调器)——VDOM 的实现,负责根据自变量变化计算出 UI 变化。
  • Renderer(渲染器)——负责将 UI 变化渲染到宿主环境中。

在新架构中,Reconciler 中的更新流程从递归变成了“可中断的循环过程”。每次循环都会调用 shouldYield 判断当前 Time Slice 是否有剩余时间,没有剩余时间则暂停更新流程,将主线程交给渲染流水线,等待下一个宏任务再继续执行,这就是 Time Slice的实现原理:


function workLoopConcurrent() { 
// 一直执行任务,直到任务执行完或中断
while (workInProgress !== null && !shouldYield()) { 
 performUnitOfWork(workInProgress); 
 } 
}

shouldYield 方法如下:


function shouldYield() { 
// 当前时间是否大于过期时间
// 其中 deadline = getCurrentTime() + yieldInterval 
// yieldInterval 为调度器预设的时间间隔,默认为 5ms 
return getCurrentTime() >= deadline; 
}

过期时间 deadline 在任务执行时被更新为“当前时间+时间间隔”,时间间隔默认为5ms,这也是图 2-3 中每个 Time Slice 宏任务的时间长度是 5ms 左右的原因。

当 Scheduler 将调度后的任务交给 Reconciler 后,Reconciler 最终会为 VDOM 元素标记各种副作用 flags,比如:


// 代表插入或移动元素
export const Placement = 0b00000000000000000000000010; 
// 代表更新元素
export const Update = 0b00000000000000000000000100; 
// 代表删除元素
export const Deletion = 0b00000000000000000000001000;

Scheduler 与 Reconciler 的工作都在内存中进行。只有当 Reconciler 完成工作后,工作流程才会进入 Renderer。

Renderer 根据“Reconciler 为 VDOM 元素标记的各种 flags”执行对应操作,比如,如上三个 flags 在浏览器宿主环境中对应三种 DOM 操作。

下面的示例1演示了上述三个模块如何配合工作:count 默认值为 0,每次点击按钮执行 count++,UL 中三个 LI 的内容分别为“1、2、3 乘以 count 的结果”。

示例1:


export default () => { 
const [count, updateCount] = useState(0); 
return ( 
<ul>
<button onClick={() => updateCount(count + 1)}>乘以{count}</button>
<li>{1 * count}</li>
<li>{2 * count}</li>
<li>{3 * count}</li>
</ul>
 ) 
}

对应工作流程如图1所示。

虚线框中的工作流程随时可能由于以下原因被中断:

 有其他更高优先级任务需要先执行;

 当前 Time Slice 没有剩余时间;

 发生错误。

图 1 新 React 架构工作流程示例

由于虚线框内的工作都在内存中进行,不会更新宿主环境 UI,因此即使工作流程反复中断,用户也不会看到“更新不完全的 UI”。

02

主打特性的迭代

随着 React 架构的重构,上层主打特性也随之迭代。按照“主打特性”划分,React大体经历了四个发展时期:

(1)Sync(同步);

(2)Async Mode(异步模式);

(3)Concurrent Mode(并发模式);

(4)Concurrent Feature(并发特性)

其中,旧架构对应同步时期。异步模式、并发模式、并发特性三个时期与新架构相关。本节主要讲解异步模式、并发模式、并发特性的演进过程。

之前曾提到“CPU 瓶颈”与“I/O 瓶颈”,React 并不是同时解决这两个问题的。首先解决的是“CPU 瓶颈”,解决方式是“架构重构”。重构后Reconciler 的工作流程从“同步”变为“异步、可中断”。正因如此,这一时期的 React被称为 Async Mode。

单一更新的工作流程变为“异步、可中断”并不能完全突破“I/O 瓶颈”,解决问题的关键在于“使多个更新的工作流程并发执行”。所以,React 继续迭代为 Concurrent Mode(并发模式)。在 React 中,Concurrent(并发)概念的意义是“使多个更新的工作流程可以并发执行”。

以上便是从 Sync 到 Async Mode 再到 Concurrent Mode 的演进过程。下一节将讲解从 Concurrent Mode 到 Concurrent Feature 的演进过程。

03

渐进升级策略的迭代

从最初的版本到 v18 版本,React 有多少个版本?从架构角度进行概括,所有 React版本一定属于如下四种情况之一。

情况 1:旧架构(v15 及之前版本属于这种情况)。

情况 2:新架构,未开启并发更新,与情况 1 行为一致(v16、v17 默认属于这种情况)。

情况 3:新架构,未开启并发更新,但是启用了一些新功能(比如 AutomaticBatching)。

情况 4:新架构,已开启并发更新。

React 团队希望:使用旧版本的开发者可以逐步升级到新版本,即从情况 1、2、3向情况 4 升级。但是升级过程中存在较大阻力,因为在情况 4 下,React 的一些行为与情况 1、2、3 不同。比如以下三个生命周期函数在情况 4 的 React 下是“不安全的”:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

强制升级可能造成代码不兼容。为了使 React 的新旧版本之间实现平滑过渡,React团队采用了“渐进升级”方案。该方案的第一步是规范代码。v16.3 新增了 StrictMode,针对开发者编写的“不符合并发更新规范的代码”给出提示,逐步引导开发者编写规范代码。比如,使用上述“不安全的”生命周期函数时会产生如图2所示的报错信息。

图2 StrictMode 下使用不安全生命周期函数报错

下一步,React 团队允许“不同情况的 React”在同一个页面共存,借此使“情况 4的 React”逐步渗透至原有项目中。具体做法是提供了以下三种开发模式:

  1. Legacy 模式,通过 ReactDOM.render(<App />, rootNode)创建的应用遵循该模式。默认关闭 StrictMode,表现同情况 2。
  2. Blocking模式 通过 ReactDOM.createBlockingRoot(rootNode).render(<App />)创建的应用遵循该模式,作为从 Legacy 向 Concurrent 过渡的中间模式,默认开启StrictMode,表现同情况 3。
  3. Concurrent 模式,通过 ReactDOM.createRoot(rootNode).render(<App />)创建的应用遵循该模式,默认开启 StrictMode,表现同情况 4。

三种开发模式支持特性对比如图3所示

图3 三种开发模式支持特性对比

为了使不同模式的应用可以在同一个页面内工作,需要对一些底层实现进行调整。比如:调整之前,大多数事件会统一冒泡到 HTML 元素,调整后则冒泡到“应用所在根元素”。这些调整工作发生在 v17,所以v17 也被称作“为开启并发更新做铺垫”的“垫脚石”版本。

2021 年 6 月 8 日,v18 工作组成立。在与社区进行大量沟通后,React 团队意识到当前的“渐进升级”策略存在两方面问题。首先,由于模式影响的是整个应用,因此无法在同一个应用中完成渐进升级。举例说明,开发者将应用中 ReactDOM.render 改为ReactDOM.createBlockingRoot,从 Legacy 模式切换到 Blocking 模式,会自动开启StrictMode。此时,整个应用的“并发不兼容警告”都会上报,开发者需要修复整个应用中的不兼容代码。从这个角度看,“渐进升级”的目的并没有达到。

其次,React 团队发现:开发者从新架构中获益,主要是由于使用了并发特性,并发特性指“开启并发更新后才能使用的那些 React 为了解决 CPU 瓶颈、I/O 瓶颈而设计的特性”,比如:

  • useDeferredValue
  • useTransition

所以,React 团队提出新的渐进升级策略——开发者仍可以在默认情况下使用同步更新,在使用并发特性后再开启并发更新。

在 v18 中运行示例2所示代码,由于 updateCount 在 startTransition 的回调函数中执行(使用了并发特性),因此 updateCount 会触发并发更新。如果 updateCount 没有在startTransition 的回调函数中执行,那么 updateCount 将触发默认的同步更新。

示例2:


const App = () => { 
const [count, updateCount] = useState(0); 
const [isPending, startTransition] = useTransition(); 
const onClick = () => { 
// 使用了并发特性 useTransition 
 startTransition(() => { 
// 本次更新是并发更新
 updateCount((count) => count + 1); 
 }); 
 }; 
return <h3 onClick={onClick}>{count}</h3>; 
};;

读者可以调试在线示例中这两种情况的调用栈火焰图,根据火焰图中观察到的 “是否开启 Time Slice”来区分“是否是并发更新”。

所以,React 在 v18 中不再提供三种开发模式,而是以“是否使用并发特性”作为“是否开启并发更新”的依据。

具体来说,开发者在 v18 中统一使用 ReactDOM.createRoot 创建应用。当不使用并发特性时,表现如情况 3。使用并发特性后,表现如情况 4。


本文节选自卡颂的新书《React设计原理》,基于React18,从理念、架构、实现三个层面解构React

这本书存在两条脉络:

  • 抽象层级逐渐降低
  • 实现越来越复杂的模块

对于前者,本书的抽象层级会逐渐从理念到架构,最后到实现,每一层都屏蔽前一层的影响。

这也是为什么ReactDOM.createRoot这个初始化API会放到第六章再讲解 —— 在这个具体API的背后,是他的理念与架构。

对于后者,本书会从0实现与react相关的6个模块,最后我们会一起在React源码内实现一个新的原生Hook

发布:刘恩惠

审核:陈歆懿

本文转载于网络 如有侵权请联系删除

相关文章

  • qiankun 实战(一)

    前言前两天用了一下微前端框架icestark,在实际架构搭建过程中发现中发现在Vue主应用子应用之间切换tag(tag分别主应用和子应用的页面)页签时会有子应用数据状态无法保存的情况,搜索了一波解决方案后发现,icestark中React应用实现了对数据状态的缓存,Vue里面没有这个实现。React实现的思路是通过Tabs组件结合icestark实现的一种机制,但是没有用到路由。由于架构时间有限,发现按照那个方案调整是实现方面时间代价有点大,尝试了一下qiankun发现框架中可以不存在这个问题,所以决定更换微前端框架方案为qianun。如果想了解icestark可以看如下文章,里面有一些关于微前端架构理念的思考快速上手微前端框架icestark(一)快速上手微前端框架icestark(二)主应用接入qiankun本地使用vue-cli创建了一个Vue2.0纯净项目作为主应用,执行yarnaddqiankun命名安装qiankun,在main.js中引入qiankun,注册并启动importVuefrom'vue' importAppfrom'./App.v

  • 老板,用float存储金额为什么要扣我工资

    背景公司最近在做交易系统,交易系统肯定是要和钱打交道的,和钱有关,自然而然很容易想到用float存储,但是使用float存储金额做的计算是近似计算。老板:用float做计算造成公司损失的钱都往你工资里扣 哼,扣工资就扣工资。但还是得静下心来想想为什么不能用float为什么不能使用float存储金额首先看个例子:FloatTest.javapublicclassFloatTest{ publicstaticvoidmain(String[]args){ floatf1=6.6f; floatf2=1.3f; System.out.println(f1+f2); } }复制结果:7.8999996和自己口算的值竟然不一样计算机只认识0和1,所有类型的计算首先会转化为二进制的计算从计算机二进制角度计算6.6+1.3的过程float底层存储计算是由CPU来完成的,CPU表示浮点数由三部分组成分为三个部分,符号位(sign),指数部分(exponent)和有效部分(fraction,mantissa)。其中float总共占用32位,符号位,指数部分,有效部分各占1位,8位,23位二进制的转化对于

  • 使用 Django + Vue.js 开发个人博客网站(完整版附源码)—— Python-课程设计-期末项目

    页面展示:源码 首页 不同板块 注册页面 登录页面 个人信息 写文章页面 文章详情页面 文章评论 本文主要讲解Python后端部分,由于仅仅用到了vue作为js框架并非前后端分离项目,故前端不单独介绍。一、项目内容(做什么)本项目实现了一个网页端的博客系统,该博客系统允许多人注册登录,用户可以在网站上面发布博客,浏览别人发布的博客。实际意义在于:当一个小组或者一个班级需要进行学习与交流的时候可以用到,大家都可以在上面分享自己的学习心得,然后互相学习,由于本项目分了板块,所以要查找相关的技术栈也很方便。项目主要模块需求驱动开发,先分析需求(这里参考了网上其它博客系统的需求): 本项目实现的主要功能:只要用户有手机号就可以进行注册登录,注册的时候需要图片验证码和手机短信验证码;用户登陆之后可以选择记住我,这样就算下次关闭了浏览器也可以实现自动登陆;用户可以修改个人信息,包括上传头像等;博客按照类型进行分类,管理员可以在后台管理页面修改具体分为哪几类;用户可以在线写博客,文本编辑器采用的是富文本编辑器,用户使用图形化界面即可写出HTML代码存储在数据库中;用户可以查看所有人

  • LeetCode677. 键值映射(Trie树)

    1.题目实现一个MapSum类里的两个方法,insert和sum。对于方法insert,你将得到一对(字符串,整数)的键值对。字符串表示键,整数表示值。如果键已经存在,那么原来的键值对将被替代成新的键值对。对于方法sum,你将得到一个表示前缀的字符串,你需要返回所有以该前缀开头的键的值的总和。输入:insert("apple",3),输出:Null 输入:sum("ap"),输出:3 输入:insert("app",2),输出:Null 输入:sum("ap"),输出:5复制2.Trie树解题参考:Trie树classTrieNode { public: TrieNode*next[26]; intcount; TrieNode():count(0) { memset(next,NULL,26*sizeof(TrieNode*)); } ~TrieNode(){} }; classMapSum{ TrieNode*root; public: /**Initializeyourdatast

  • SpringBoot配置logback

    配置日志文件springboot默认会加载classpath:logback-spring.xml或者classpath:logback-spring.groovy。如需要自定义文件名称,在application.properties中配置logging.config选项即可。在src/main/resources下创建logback-spring.xml文件,内容如下:<?xmlversion="1.0"encoding="UTF-8"?> <configuration> <!--文件输出格式--> <propertyname="PATTERN"value="%-12(%d{yyyy-MM-ddHH:mm:ss.SSS})|-%-5level[%thread]%c[%L]-|%msg%n"/> <!--test文件路径--> <propertyname="TEST_FILE_PATH"value="d:/tes

  • 3Com SuperStack 3 Sw

    一般情况下用4900默认的配置就可以,根据情况也可以进行修改。主要的包括设置网管软件对交换机进行读/写的Comunity字串,以及交换机向网管软件发送Trap的目的地址,设置如下所示:--------------------------------------------------------------------------------Selectmenuoption:sysmansnmpcommEnternewcommunityforuser'admin'[private]:write001Enternewcommunityforuser'manager'[manager]:write000Enternewcommuntiyforuser'monitor'[public]:read001Selectmenuoption:--------------------------------------------------------------------------------[说明]以上设置将网管系统读的Commun

  • 利用jTessBoxEditor工具进行Tesseract3.02.02样本训练,提高验证码识别率

    1、背景前文已经简要介绍tesseractocr引擎的安装及基本使用,其中提到使用-leng参数来限定语言库,可以提高识别准确率及识别效率。本文将针对某个网站的验证码进行样本训练,形成自己的语言库,来提高验证码识别率。2、准备工具tesseract样本训练有一个官方流程说明,https://github.com/tesseract-ocr/tesseract/wiki/TrainingTesseract#run-tesseract-for-training,不过都是英文的,个人认为这个地址适合于查找细节问题,全程看E文对大众还是有一定的困难。具体的方法有两种:1-利用三方工具,2-完全命令行操作,三方工具主要在https://github.com/tesseract-ocr/tesseract/wiki/AddOns下载,本文将用到jTessBoxEditor这个工具,我们先给他下载到本地。需要特别说明,这个工具是基于java虚拟机运行的,所以我们还要下载并安装一个java虚拟机,下载地址:http://download.oracle.com/otn-pub/java/jdk/8u91

  • hadoop商业版本选择对比

    hadoop商业版本选择对比记得刚接触到hadoop的时候跟大部分人一样都会抱怨hadoop的安装部署问题,对于一个新手来说这这的是个头疼的问题,可能需要花费一整天的时间才能把分布式环境安装配置好。在刚接触hadoop的一段时间里,可以说对于hadoop的理解一直都是停留在相对较肤浅的层面。后来随着自己的不断摸索以及向圈内的前辈大神请教交流(主要是向大神请教学来的),自己对于hadoop的认识以及应用也就更加娴熟。作为一个过来人,在这里给新人分享一些关于hadoop版本选择的问题,希望别像我当时傻乎乎的只知道hadoop有1.0.x和2.x版本。当前hadoop的发行版本除了Apache的开元版本之外,华为发行版、Intel发行版以及Cloudera发行版等。上面说的这几个第三方的发行版已经有相对较长的一些时间,除此之外还有最近几年异军突起的DKhadoop商业发行版。国内的大多数公司推出的Hadoop发行版都是收费的,免费的发行版则主要是国外的,比如Apache的发行版、Cloudera发行版等。面对如此多的hadoop版本不免会让人难以选择。下面我们就简单对比一些这些不同版本的优缺

  • 正态分布为什么常见?

    统计学里面,正态分布(normaldistribution)最常见。男女身高、寿命、血压、考试成绩、测量误差等等,都属于正态分布。以前,我认为中间状态是事物的常态,过高和过低都属于少数,这导致了正态分布的普遍性。最近,读到了JohnD.Cook的文章,才知道我的这种想法是错的。正态分布为什么常见?真正原因是中心极限定理(centrallimittheorem)。"多个独立统计量的和的平均值,符合正态分布。" 上图中,随着统计量个数的增加,它们和的平均值越来越符合正态分布。根据中心极限定理,如果一个事物受到多种因素的影响,不管每个因素本身是什么分布,它们加总后,结果的平均值就是正态分布。举例来说,人的身高既有先天因素(基因),也有后天因素(营养)。每一种因素对身高的影响都是一个统计量,不管这些统计量本身是什么分布,它们和的平均值符合正态分布。(注意:男性身高和女性身高都是正态分布,但男女混合人群的身高不是正态分布。)许多事物都受到多种因素的影响,这导致了正态分布的常见。读到这里,读者可能马上就会提出一个问题:正态分布是对称的(高个子与矮个子的比例相同),但是很多真实世

  • openresty源码剖析——lua代码的执行

    上一篇文章中(https://cloud.tencent.com/developer/article/1037840)我们讨论了openresty是如何加载lua代码的那么加载完成之后的lua代码又是如何执行的呢##代码的执行 在init_by_lua等阶段 openresty是在主协程中通过lua_pcall直接执行lua代码而在access_by_lua content_by_lua等阶段中,openresty创建一个新的协程,通过lua_resume执行lua代码二者的区别在于能否执行ngx.slepp.ngx.threadngx.socket这些有让出操作的函数我们依旧以content_by_**阶段为例进行讲解#content_by_**阶段content_by_**阶段对应的请求来临时,执行流程为ngx_http_lua_content_handler->ngx_http_lua_content_handler_file->ngx_http_lua_content_by_chunkngx_http_lua_content_handler和ngx_http_lua

  • 字符串常量池

    作为最基础的引用数据类型,Java设计者为String提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字符串常量池的设计意图是什么? 字符串常量池在哪里? 如何操作字符串常量池? 字符串常量池的设计思想 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能 JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化 为字符串开辟一个字符串常量池,类似于缓存区 创建字符串常量时,首先坚持字符串常量池是否存在该字符串 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中 实现的基础 实现该优化的基础是因为字符串是不可变的,可以不用担心数据冲突进行共享 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收 代码:从字符串常量池中获取相应的字符串

  • hdu 3639 有向图缩点+建反向图+搜索

    题意:给个有向图,每个人可以投票(可以投很多人,一次一票),但是一个人只能支持一人一次,支持可以传递,自己支持自己不算,被投支持最多的人。 开始想到缩点,然后搜索,问题是有一点想错了!以为支持按票数计算,而不是按人数!还各种树形dp/搜索求可以到达边数。。提交WA了。。。 又反复读了题目之后才发现。。错误。。只要人数就行。。。问题简单了许多。。。 缩点成有向无环图后:每个SCC中支持的人数就是scc里面的人,要求可到达的点最多的点,当然要放过来求方便:反向图那个点可以到达的点最多!于是建反向图直接dfs即可,记录答案有点麻烦。。。提交就A了。。。这题总体花了我2多个小时。。算失败。。。 #include<iostream> #include<stack> #include<queue> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> usingnamespacestd; constintinf=0x3f3f3

  • 安全测试16--漏洞扫描工具Nikto详细使用教程

    Nikto是一个开源的WEB扫描评估软件,可以对Web服务器进行多项安全测试,能在230多种服务器上扫描出2600多种有潜在危险的文件、CGI及其他问题。Nikto可以扫描指定主机的WEB类型、主机名、指定目录、特定CGI漏洞、返回主机允许的http模式等。 工具使用 打开Kali工具列表,点击02-漏洞扫描,选择niktoLOGO,会打开Terminal终端     Nikto帮助 在终端中我们可以使用nikto命令查看帮助信息,或者通过nikto-H、mannikto查看更详细的帮助信息。       Nikto插件 Nikto通过大量插件进行扫描,我们可以通过 nikto-list-plugins 来查看插件信息    可以通过nikto-V来查看工具版本和插件版本     常规扫描 在终端中使用nikto-host/-hhttp://www.example.com进行扫描     指定端口扫描 使用命令nikto-h http:/

  • win10安装Docker

    win10安装Docker 一、启用系统的Hper-V和虚拟化设置 控制面板->程序->程序和功能->启用或关闭windows功能->Hyper-V 由于自己的系统是win10家庭版,没有Hyper-V选项,因此使用下面的代码来安装,将其保存为脚本Hyper.cmd,然后使用管理员权限运行就可以了,之后会提示重启系统,重启就可以了。 pushd"%~dp0" dir/b%SystemRoot%\servicing\Packages\*Hyper-V*.mum>hyper-v.txt for/f%%iin('findstr/i.hyper-v.txt2^>nul')dodism/online/norestart/add-package:"%SystemRoot%\servicing\Packages\%%i" delhyper-v.txt Dism/online/enable-feature/featurename:Microsoft-Hyper-V-All/LimitAccess/ALL 复制 二、下载并安装Docker 官方下载地址:Docke

  • pyCharm安装

    PyCharm是一款功能强大的Python编辑器,具有跨平台性,给编程者带来不少方便。在Windons环境安装。 PyCharm下载地址:https://www.jetbrains.com/pycharm/download/#section=windows 进入该网站后,我们会看到如下界面:      professional是专业版,community是社区版,根据需求选择自己想要的版本,professional功能比community多,但不是免费的,community是一款免费的。 当下载好以后,双击安装包,点击Next 选择自己想要存放的路径,我这里安装默认,选择好路径后,Next 这里我们选择64位系统与添加环境变量 点击Install,等待安装成功 如果我们之前没有下载有Python解释器的话,在python官方网站下载一个 Python开发环境下载地址:https://www.python.org/downloads/   启动pyCharm 点击CreateNewProject创建新的项目,如下界面:   选择

  • 联合索引特殊案例

    CREATETABLEt(    c1varchar(10)notnull,   c2varchar(10)notnull,   c3varchar(10)notnull,   c4varchar(10)notnull,   c5varchar(10)notnull )ENGINEInnoDBCHARSETUTF8;       altertabletaddindexc1234(c1,c2,c3,c4);     insertintotVALUES('1','1','1','1','1'),('2','2','2','2','2'),('3','3','3','3','3'),('4','4','4','4','4'),('5','5','5','5','5'); 案例1:不影响索引走四个 explainselect*fromtwherec1like'3'andc2='1'andc3='1'andc

  • Django中ORM介绍和字段及字段参数

    https://www.cnblogs.com/liwenzhou/p/8688919.html   Django中ORM介绍和字段及字段参数   ObjectRelationalMapping(ORM) ORM介绍 ORM概念 对象关系映射(ObjectRelationalMapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。 ORM在业务逻辑层和数据库层之间充当了桥梁的作用。 ORM由来 让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。 几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。 按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的。 ORM的优势 ORM

  • 使用electron-builder打包时下载electron失败解决方案

    electron-builder在打包时会检测cache中是否有electron包,如果没有的话会从github上拉去,在国内网络环境中拉取的过程大概率会失败,所以你可以自己去下载一个包放到cache目录里 各个平台的目录地址 Linux:$XDG_CACHE_HOMEor~/.cache/electron/ MacOS:~/Library/Caches/electron/ Windows:%LOCALAPPDATA%/electron/Cacheor~/AppData/Local/electron/Cache/ 参考:https://github.com/electron/get#how-it-works 例如在macos平台打包electron应用,执行electron-builder--mac--x64 ➜clipboardgit:(master)✗npmrundist >clipboard@1.0.0dist/Users/xx/workspace/electron/clipboard >electron-builder--mac--x64 •electron-b

  • php--获取用户ip

    一般在做登录的时候有的会要求同一个帐号不能同时用不同的ip登录,这个时候我们需要获取到用户IP地址 获取ip地址的函数: functiongetIP(){ if(getenv('HTTP_CLIENT_IP')){ $ip=getenv('HTTP_CLIENT_IP'); }elseif(getenv('HTTP_X_FORWARDED_FOR')){ $ip=getenv('HTTP_X_FORWARDED_FOR'); }elseif(getenv('HTTP_X_FORWARDED')){ $ip=getenv('HTTP_X_FORWARDED'); }elseif(getenv('HTTP_FORWARDED_FOR')){ $ip=getenv('HTTP_FORWARDED_FOR'); }elseif(getenv('HTTP_FORWARDED')){ $ip=getenv('HTTP_FORWARDED'); }else{ $ip=$_SERVER['REMOTE_ADDR']; } return$ip;} 活着不应该靠泪水博取同情,而是需要靠汗水

  • 解决&quot;Uncaught (in promise) Error: Navigation cancelled from &quot;/&quot; to &quot;/login&quot; with a new navigation&quot;报错处理

     Uncaught(inpromise)Error:Navigationcancelledfrom“/”to“/login”withanewnavigation. 这个错误是vue-router内部错误,没有进行catch处理,导致的编程式导航跳转问题,往同一地址跳转时会报错的情况。 push和replace都会导致这个情况的发生。   解决方法如下: 在路由器中进行配置:router/index.js importVuefrom'vue' importRouterfrom'vue-router' Vue.use(Router) //解决报错 constoriginalPush=Router.prototype.push constoriginalReplace=Router.prototype.replace //push Router.prototype.push=functionpush(location,onResolve,onReject){ if(onResolve||onReject)returnoriginalPush.call(this,lo

  • Spring Boot项目——自定义HandlerMethodArgumentResolver获取当前登陆用户

    思路 自定义注解:指定参数 创建登陆用户类:保存登陆用户信息 自定义登陆用户参数解析器:获取登陆token,解析为登陆用户对象信息 配置MVC:新增登陆用户参数解析器 代码 自定义LoginUser注解 packagecom.canaan.manager.token; importjava.lang.annotation.ElementType; importjava.lang.annotation.Retention; importjava.lang.annotation.RetentionPolicy; importjava.lang.annotation.Target; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public@interfaceLoginUser{ }复制 创建CurrentUser类 packagecom.canaan.manager.token; importjava.util.Date; /* *@DescriptionjwtToken用

相关推荐

推荐阅读