当前位置: 首页 > news >正文

React源码分析(二)渲染机制

准备工作

为了方便讲解,假设我们有下面这样一段代码:

function App(){const [count, setCount] = useState(0)useEffect(() => {setCount(1)}, [])const handleClick = () => setCount(count => count++)return (<div>勇敢牛牛,        <span>不怕困难</span><span onClick={handleClick}>{count}</span></div>)
}ReactDom.render(<App />, document.querySelector('#root'))

在React项目中,这种jsx语法首先会被编译成:

React.createElement("App", null)
or
jsx("App", null)

这里不详说编译方法,感兴趣的可以参考:

babel在线编译

新的jsx转换

jsx语法转换后,会通过creatElementjsx的api转换为React element作为ReactDom.render()的第一个参数进行渲染。

在上一篇文章Fiber中,我们提到过一个React项目会有一个fiberRoot和一个或多个rootFiberfiberRoot是一个项目的根节点。我们在开始真正的渲染前会先基于rootDOM创建fiberRoot,且fiberRoot.current = rootFiber,这里的rootFiber就是currentfiber树的根节点。

if (!root) {// Initial mountroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);fiberRoot = root._internalRoot;
}

在创建好fiberRootrootFiber后,我们还不知道接下来要做什么,因为它们和我们的<App />函数组件没有一点关联。这时React开始创建update,并将ReactDom.render()的第一个参数,也就是基于<App />创建的React element赋给update

var update = {eventTime: eventTime,lane: lane,tag: UpdateState,payload: null,callback: element,next: null};

有了这个update,还需要将它加入到更新队列中,等待后续进行更新。在这里有必要讲下这个队列的创建流程,这个创建操作在React有多次应用。

var sharedQueue = updateQueue.shared;var pending = sharedQueue.pending;if (pending === null) {   // mount时只有一个update,直接闭环update.next = update;} else {   // update时,将最新的update的next指向上一次的update, 上一次的update的next又指向最新的update形成闭环update.next = pending.next;pending.next = update;}// pending指向最新的update, 这样我们遍历update链表时, pending.next会指向第一个插入的update。sharedQueue.pending = update;   

我将上面的代码进行了一下抽象,更新队列是一个环形链表结构,每次向链表结尾添加一个update时,指针都会指向这个update,并且这个update.next会指向第一个更新:

image.png

上一篇文章也讲过,React最多会同时拥有两个fiber树,一个是currentfiber树,另一个是workInProgressfiber树。currentfiber树的根节点在上面已经创建,下面会通过拷贝fiberRoot.current的形式创建workInProgressfiber树的根节点。

到这里,前面的准备工作就做完了, 接下来进入正菜,开始进行循环遍历,生成fiber树和dom树,并最终渲染到页面中。

render阶段

这个阶段并不是指把代码渲染到页面上,而是基于我们的代码画出对应的fiber树和dom树。

workloopSync

function workLoopSync() {while (workInProgress !== null) {performUnitOfWork(workInProgress);}
}

在这个循环里,会不断根据workInProgress找到对应的child作为下次循环的workInProgress,直到遍历到叶子节点,即深度优先遍历。在performUnitOfWork会执行下面的beginWork

image.png

beginWork

简单描述下beginWork的工作,就是生成fiber树。

基于workInProgress的根节点生成<App />fiber节点并将这个节点作为根节点的child,然后基于<App />fiber节点生成<div />fiber节点并作为<App />fiber节点的child,如此循环直到最下面的牛牛文本。

image.png

注意, 在上面流程图中,updateFunctionComponent会执行一个renderWithHooks函数,这个函数里面会执行App()这个函数组件,在这里会初始化函数组件里所有的hooks,也就是上面实例代码的useState()

当遍历到牛牛文本时,它的下面已经没有了child,这时beginWork的工作就暂时告一段落,为什么说是暂时,是因为在completeWork时,如果遍历的fiber节点有sibling会再次走到beginWork。相关参考视频讲解:进入学习

completeWork

当遍历到牛牛文本后,会进入这个completeWork

在这里,我们再简单描述下completeWork的工作, 就是生成dom树。

基于fiber节点生成对应的dom节点,并且将这个dom节点作为父节点,将之前生成的dom节点插入到当前创建的dom节点。并会基于在beginWork生成的不完全的workInProgressfiber树向上查找,直到fiberRoot。在这个向上的过程中,会去判断是否有sibling,如果有会再次走beginWork,没有就继续向上。这样到了根节点,一个完整的dom树就生成了。

image.png

额外提一下,在completeWork中有这样一段代码

if (flags > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = completedWork;} else {returnFiber.firstEffect = completedWork;}returnFiber.lastEffect = completedWork;
}

解释一下, flags > PerformedWork代表当前这个fiber节点是有副作用的,需要将这个fiber节点加入到父级fibereffectList链表中。

commit阶段

这个阶段的主要工作是处理副作用。所谓副作用就是不确定操作,比如:插入,替换,删除DOM,还有useEffect()hook的回调函数都会被作为副作用。

commitWork

准备工作

commitWork前,会将在workloopSync中生成的workInProgressfiber树赋值给fiberRootfinishedWork属性。

var finishedWork = root.current.alternate;  // workInProgress fiber树
root.finishedWork = finishedWork;  // 这里的root是fiberRoot
root.finishedLanes = lanes;
commitRoot(root);

在上面我们提到,如果一个fiber节点有副作用会被记录到父级fiberlastEffectnextEffect
在下面代码中,如果fiber树有副作用,会将rootFiber.firstEffect节点作为第一个副作用firstEffect,并且将effectList形成闭环。

var firstEffect;
// 判断当前rootFiber树是否有副作用
if (finishedWork.flags > PerformedWork) {// 下面代码的目的还是为了将这个effectList链表形成闭环if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}
} else {
// 这个rootFiber树没有副作用
firstEffect = finishedWork.firstEffect;
}

mutation之前

简单描述mutation之前阶段的工作:

  • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑;
  • 调用getSnapshotBeforeUpdate,fiberRoot和ClassComponent会走这里;
  • 调度useEffect(异步);
    在mutation之前的阶段,遍历effectList链表,执行commitBeforeMutationEffects方法。
do {  // mutation之前invokeGuardedCallback(null, commitBeforeMutationEffects, null);} while (nextEffect !== null);

我们进到commitBeforeMutationEffects方法,我将代码简化一下:

function commitBeforeMutationEffects() {while (nextEffect !== null) {var current = nextEffect.alternate;// 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑;if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null){...}var flags = nextEffect.flags;// 调用getSnapshotBeforeUpdate,fiberRoot和ClassComponent会走这里if ((flags & Snapshot) !== NoFlags) {...}// 调度useEffect(异步)if ((flags & Passive) !== NoFlags) {// rootDoesHavePassiveEffects变量表示当前是否有副作用if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;// 创建任务并加入任务队列,会在layout阶段之后触发scheduleCallback(NormalPriority$1, function () {flushPassiveEffects();return null;});}}// 继续遍历下一个effectnextEffect = nextEffect.nextEffect;}
}

按照我们示例代码,我们重点关注第三件事,调度useEffect(注意,这里是调度,并不会马上执行)。

scheduleCallback主要工作是创建一个task

var newTask = {id: taskIdCounter++,callback: callback,  //上面代码传入的回调函数priorityLevel: priorityLevel,startTime: startTime,expirationTime: expirationTime,sortIndex: -1
};

它里面有个逻辑会判断startTimecurrentTime, 如果startTime > currentTime,会把这个任务加入到定时任务队列timerQueue,反之会加入任务队列taskQueue,并task.sortIndex = expirationTime

mutation

简单描述mutation阶段的工作就是负责dom渲染。

区分fiber.flags,进行不同的操作,比如:重置文本,重置ref,插入,替换,删除dom节点。

和mutation之前阶段一样,也是遍历effectList链表,执行commitMutationEffects方法。

do {    // mutation  dom渲染invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel);} while (nextEffect !== null);

看下commitMutationEffects的主要工作:

function commitMutationEffects(root, renderPriorityLevel) {// TODO: Should probably move the bulk of this function to commitWork.while (nextEffect !== null) {     // 遍历EffectListsetCurrentFiber(nextEffect);// 根据flags分别处理var flags = nextEffect.flags;// 根据 ContentReset flags重置文字节点if (flags & ContentReset) {...}// 更新refif (flags & Ref) {...}var primaryFlags = flags & (Placement | Update | Deletion | Hydrating);switch (primaryFlags) {case Placement:   // 插入dom{...}case PlacementAndUpdate:    //插入dom并更新dom{// PlacementcommitPlacement(nextEffect);nextEffect.flags &= ~Placement; // Updatevar _current = nextEffect.alternate;commitWork(_current, nextEffect);break;}case Hydrating:     //SSR{...}case HydratingAndUpdate:      // SSR{...}case Update:      // 更新dom{...}case Deletion:    // 删除dom{...}}resetCurrentFiber();nextEffect = nextEffect.nextEffect;}
}

按照我们的示例代码,这里会走PlacementAndUpdate,首先是commitPlacement(nextEffect)方法,在一串判断后,最后会把我们生成的dom树插入到rootDOM节点中。

function appendChildToContainer(container, child) {var parentNode;if (container.nodeType === COMMENT_NODE) {parentNode = container.parentNode;parentNode.insertBefore(child, container);} else {parentNode = container;parentNode.appendChild(child);    // 直接将整个dom作为子节点插入到root中}
}

到这里,代码终于真正的渲染到了页面上。下面的commitWork方法是执行和useLayoutEffect()有关的东西,这里不做重点,后面文章安排,我们只要知道这里是执行上一次更新effect unmount

fiber树切换

在讲layout阶段之前,先来看下这行代码

root.current = finishedWork  // 将`workInProgress`fiber树变成`current`树

这行代码在mutation和layout阶段之间。在mutation阶段, 此时的currentfiber树还是指向更新前的fiber树, 这样在生命周期钩子内获取的DOM就是更新前的, 类似于componentDidMountcompentDidUpdate的钩子是在layout阶段执行的,这样就能获取到更新后的DOM进行操作。

layout

简单描述layout阶段的工作:

  • 调用生命周期或hooks相关操作
  • 赋值ref

和mutation之前阶段一样,也是遍历effectList链表,执行commitLayoutEffects方法。

do {   // 调用生命周期和hook相关操作, 赋值refinvokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
} while (nextEffect !== null);

来看下commitLayoutEffects方法:

function commitLayoutEffects(root, committedLanes) {while (nextEffect !== null) {setCurrentFiber(nextEffect);var flags = nextEffect.flags;// 调用生命周期或钩子函数if (flags & (Update | Callback)) {var current = nextEffect.alternate;commitLifeCycles(root, current, nextEffect);}{// 获取dom实例,更新refif (flags & Ref) {commitAttachRef(nextEffect);}}resetCurrentFiber();nextEffect = nextEffect.nextEffect;}
}

提一下,useLayoutEffect()的回调会在commitLifeCycles方法中执行,而useEffect()的回调会在commitLifeCycles中的schedulePassiveEffects方法进行调度。从这里就可以看出useLayoutEffect()useEffect()的区别:

  • useLayoutEffect上次更新销毁函数mutation阶段销毁,本次更新回调函数是在dom渲染后的layout阶段同步执行;
  • useEffectmutation之前阶段会创建调度任务,在layout阶段会将销毁函数和回调函数加入到pendingPassiveHookEffectsUnmountpendingPassiveHookEffectsMount队列中,最终它的上次更新销毁函数本次更新回调函数都是在layout阶段后异步执行; 可以明确一点,他们的更新都不会阻塞dom渲染。

layout之后

还记得在mutation之前阶段的这几行代码吗?

// 创建任务并加入任务队列,会在layout阶段之后触发
scheduleCallback(NormalPriority$1, function () {flushPassiveEffects();return null;
});

这里就是在调度useEffect(),在layout阶段之后会执行这个回调函数,此时会处理useEffect上次更新销毁函数本次更新回调函数

总结

看完这篇文章, 我们可以弄明白下面这几个问题:

  1. React的渲染流程是怎样的?
  2. React的beginWork都做了什么?
  3. React的completeWork都做了什么?
  4. React的commitWork都做了什么?
  5. useEffect和useLayoutEffect的区别是什么?
  6. useEffect和useLayoutEffect的销毁函数和更新回调的调用时机?

相关文章:

React源码分析(二)渲染机制

准备工作 为了方便讲解&#xff0c;假设我们有下面这样一段代码&#xff1a; function App(){const [count, setCount] useState(0)useEffect(() > {setCount(1)}, [])const handleClick () > setCount(count > count)return (<div>勇敢牛牛, <sp…...

Object.defineProperty 和 Proxy 的区别

区别:Object.defineProperty是一个用来定义对象的属性或者修改对象现有的属性的函数&#xff0c;&#xff0c;而 Proxy 是一个用来包装普通对象的对象的对象。Object.defineProperty是vue2响应式的原理, Proxy 是vue3响应式的原理1)参数不同Object.defineProperty参数obj: 要定…...

Python基础4——面向对象

目录 1. 认识对象 2. 成员方法 2.1 成员方法的定义语法 3. 构造方法 4. 其他的一些内置方法 4.1 __str__字符串方法 4.2 __lt__小于符号比较方法 4.3 __le__小于等于符号比较方法 4.4 __eq__等号比较方法 5. 封装特性 6. 继承特性 6.1 单继承 6.2 多继承 6.3 pas…...

Hive 核心知识点灵魂 16 问

本文目录 No1. 请谈一下 Hive 的特点No2. Hive 底层与数据库交互原理&#xff1f;No3. Hive 的 HSQL 转换为 MapReduce 的过程&#xff1f;No4. Hive 的两张表关联&#xff0c;使用 MapReduce 怎么实现&#xff1f;No5. 请说明 hive 中 Sort By&#xff0c;Order By&#xff0…...

聊聊探索式测试与敏捷实践

这是鼎叔的第五十二篇原创文章。行业大牛和刚毕业的小白&#xff0c;都可以进来聊聊。欢迎关注本专栏和微信公众号《敏捷测试转型》&#xff0c;大量原创思考文章陆续推出。探索式测试在敏捷测试象限中处于右上角&#xff0c;即面向业务且评价产品&#xff0c;这篇补充一下探索…...

社区宠物诊所管理系统

目录第一章概述 PAGEREF _Toc4474 \h 21.1引言 PAGEREF _Toc29664 \h 31.2开发背景 PAGEREF _Toc3873 \h 3第二章系统总体结构及开发 PAGEREF _Toc19895 \h 32.1系统的总体设计 PAGEREF _Toc6615 \h 32.2开发运行环境 PAGEREF _Toc13054 \h 3第三章数据库设计 PAGEREF _Toc2852…...

Vue项目创建首页发送axios请求

这是个全新的Vue项目,引入了ElementUI 将App.vue里的内容干掉,剩如下 然后下面的三个文件也可以删掉了 在views文件下新建Login.vue组件 到router目录下的index.js 那么现在的流程大概是这样子的 启动 写登陆页面 <template><div><el-form :ref"form"…...

Nginx

NginxNginxNginx可以从事的用途Nginx安装Nginx自带常用命令Nginx启动Nginx停止Nginx重启Nginx配置概要第一部分&#xff1a;全局块第二部分&#xff1a;events 块&#xff1a;第三部分&#xff1a;http块&#xff1a;Nginx Nginx是一个高性能的http和反向代理服务器&#xff0…...

2049. 统计最高分的节点数目

2049. 统计最高分的节点数目题目算法设计&#xff1a;深度优先搜索题目 传送门&#xff1a;https://leetcode.cn/problems/count-nodes-with-the-highest-score/ 算法设计&#xff1a;深度优先搜索 这题的核心是计算分数。 一个节点的分数 左子树节点数 右子树节点数 除自…...

Docker 架构简介

Docker 架构 Docker 包括三个基本概念: 镜像&#xff08;Image&#xff09;&#xff1a;Docker 镜像&#xff08;Image&#xff09;&#xff0c;就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。容器&am…...

玄子Share-BCSP助学手册-JAVA开发

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b2gPyAnt-1676810001349)(./assets/%E7%8E%84%E5%AD%90Share%E4%B8%89%E7%89%88.jpg)] 玄子Share-BCSP助学手册-JAVA开发 前言&#xff1a; 此文为玄子&#xff0c;复习BCSP一二期后整理的文章&#x…...

利用React实现多个场景下的鼠标跟随框提示框

前言 鼠标跟随框的作用如下图所示&#xff0c;可以在前端页面上&#xff0c;为我们后续的鼠标操作进行提示说明&#xff0c;提升用户的体验。本文将通过多种方式去实现&#xff0c;从而满足不同场景下的需求。 实现原理 实现鼠标跟随框的原理很简单&#xff0c;就是监听鼠标在…...

【安全知识】——如何绕过cdn获取真实ip

作者名&#xff1a;白昼安全主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 以后赚大钱座右铭&#xff1a; 不要让时代的悲哀成为你的悲哀专研方向&#xff1a; web安全&#xff0c;后渗透技术每日鸡汤&#xff1a; 现在的样子是你想要的吗&#xff1f;cdn简单来说就是…...

JavaScript内存泄露和垃圾回收机制

1、是什么&#xff1f;内存泄露&#xff08;Memory leak&#xff09;是在计算机科学中&#xff0c;由于疏忽或错误造成程序未能释放已经不再使用的内存。并非指内存在物理上的消失&#xff0c;而是应用程序分配某段内存后&#xff0c;由于设计错误&#xff0c;导致在释放该段内…...

Kubernetes02:知识图谱

Kubernetes01&#xff1a;知识图谱 MESOS APACHE 分布式资源管理框架 2019-5 Twitter 》 Kubernetes Docker Swarm 2019-07 阿里云宣布 Docker Swarm 剔除 Kubernetes Google 10年容器化基础架构 borg Go语言 Borg 特点 轻量级&#xff1a;消耗资源小 开源 弹性伸缩 负载均…...

nginx-服务器banner泄漏风险

http { server_tokens off; # 隐藏Nginx版本号 .... }...

GCC 同名符号冲突解决办法

一、绪论 作为 C/C 的开发者&#xff0c;大多数都会清楚课本上动态库以及静态库的优缺点&#xff0c;在教科书上谈及到动态库的一个优点是可以节约磁盘和内存的空间&#xff0c;多个可执行程序通过动态库加载的方式共用一段代码段 &#xff1b;而时至今日&#xff0c;再看看上…...

下一代视频编码技术2023

下一代视频编码技术 下面将从这两个角度来介绍华为云视频在下一代视频编码技术上的一些工作。这些技术得益于华为2012 媒体技术院全力支持。 2.1 下一代视频编码标准技术 从上图可以看出&#xff0c;下一代的视频编码标准大概分为三个阵营或者三个类型&#xff1a; 国际标准…...

最新最全中小微企业研究数据:海量创业公司信息与获取投资信息(1985-2021年)

一、企业获取投资名单&资方信息 数据来源&#xff1a;搜企网、企查查、天眼查 时间跨度&#xff1a;1985年8月-2021年9月 区域范围&#xff1a;全国范围 数据字段&#xff1a;企业名称、时间、获得投资金额以及投资方信息 部分数据&#xff1a; DateCompany_nameUnit…...

springboot数据源浅析

DataSourceAutoConfiguration分析 SpringBoot有一个自动配置DataSourceAutoConfiguration 为数据源配置 /META-INF/spring.factories文件找到DataSourceAutoConfiguration配置类 一、先来看下DataSourceAutoConfiguration配置类生效的时机&#xff0c;观察源码发现 Configura…...

2022黑马Redis跟学笔记.实战篇(七)

2022黑马Redis跟学笔记.实战篇 七4.11.附近的店铺功能4.11.1. GEO数据结构的基本用法1. 附近商户-导入店铺数据到GEO4.11.2. 获取附近的店铺1. 附近商户-实现附近商户功能4.9. 签到功能4.9.1.BitMap原理1. 用户签到-BitMap功能演示4.9.2.实现签到功能4.9.3.实现补签功能4.9.4.统…...

QT mp3音乐播放器实现框架,Qt鼠标事件,网络编程,QSqlite,Json解析,HTTP请求等

QT mp3音乐播放器实现框架&#xff0c;Qt鼠标事件&#xff0c;网络编程&#xff0c;QSqlite,Json解析&#xff0c;HTTP请求等框架搭建UI设计mp3.hmp3.cpp隐藏窗口标题 最大化 最小化 关闭框架搭建 .pro添加 # 网络 添加多媒体 数据库 QT network multimedia sql添加头…...

硬件学习 软件Cadence day04 PCB 封装绘制

1.文章内容&#xff1a; 1. 贴片式电容 PCB 封装绘制 &#xff08;型号 c0603 &#xff09; 2. 贴片式电阻 PCB 封装绘制 &#xff08;型号 r0603 &#xff09; 3. 安规式电容 PCB 封装绘制 &#xff08;这个就是 有一个电容&#xff0c;插入一个搞好的孔里面 …...

【Java】yield()和join()区别

一、java 线程调度的背景 java虚拟机要求在多线程中实现 preemptive和priority-based调度&#xff0c;这意味着java中每一个线程被分配了特定的优先级&#xff0c;正整数在定义好的范围内不断减。优先级可以通过开发者改变但是java虚拟机从不改变线程的优先级&#xff0c;即使…...

【MySQL】Java连接MySQL数据库(封装版只需会MySQL)

一、准备普通项目如果创建的是普通的Java项目&#xff0c;我们需要去maven仓库下载jdbc驱动包然导入项目中就能使用&#xff0c;具体步骤详见MySQL数据库之Java中如何使用数据库【JDBC编程】maven项目如果创建的项目是maven项目&#xff0c;我们只需在pom.xml文件里引入一组依赖…...

【java基础】运算符

运算符 operator 运算符优先级 Operators 操作员Precedence 优先级postfix 后缀expr expr--unary 一元的expr --expr expr -expr ~ !multiplicative 〔数〕乘法的 / %additive 添加剂 -shift 移动<< >> >>>relational 关系的< > < > insta…...

带噪学习-概述

在实际应用的时候&#xff0c;我们的样本不会是完全干净的&#xff0c;即存在噪声样本。那使用存在噪声的样本时&#xff0c;我们如何更有效的进行模型学习呢&#xff1f;Label Dependent Nose样本选择&#xff08;Sample Selection&#xff09;第一种很直接的想法&#xff0c;…...

Scratch少儿编程案例-多彩打地鼠

专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...

为什么拔掉计算机网线还能ping通127.0.0.1?

前言 当我们在计算机上拔掉网线之后&#xff0c;发现我们仍然可以使用ping命令来ping通本机的IP地址127.0.0.1&#xff0c;这让很多人感到困惑&#xff0c;认为拔掉网线后计算机就无法与外界通信了&#xff0c;为什么还能ping通本机的IP地址呢&#xff1f; 本文的目的是通过对…...

Android kotlin 内、外部存储根目录及测试(可以实现仿微信未读消息数提示数字)

<<返回总目录 文章目录 一、内部存储与外部存储三、外部存储的写读测试(可以实现仿微信未读消息数提示数字)一、内部存储与外部存储 所有Android设备都有两个文件存储区域:内部存储空间(internal Storage)和外部存储空间(external Storage)。所以,Android系统从逻…...

外贸产品推广网站/唯尚广告联盟

MYSQL语法测试&#xff1a;CASE WHEN THEN LESE END 文章目录MYSQL语法测试&#xff1a;CASE WHEN THEN LESE END官方文档CASE语法语法1语法2测试语法1测试语法2测试(实用案例)注意官方文档 https://dev.mysql.com/doc/refman/5.7/en/case.html CASE语法 语法1 CASE case_v…...

用java可以做网站吗/北京网站建设公司

LEN() 函数 LEN() 函数返回文本字段中值的长度。 SQL LEN() 语法 SELECT LEN(column_name) FROM table_name; MySQL 中函数为 LENGTH(): SELECT LENGTH(column_name) FROM table_name; 演示数据库 在本教程中&#xff0c;我们将使用 RUNOOB 样本数据库。 下面是选自 "Webs…...

php除了 wordpress/谷歌广告代运营

聚合操作符 startWith操作符startWithArray操作符concat/concatArray操作符merge/mergeArray操作符concatDelayError/mergeDelayError操作符zip操作符combineLatest操作符combineLatestDelayError操作符reduce操作符count操作符collect操作符 startWith startWith操作符主要…...

wordpress微信机器人破解版/网络推广合作协议范本

羽毛球比赛规则&#xff1a; 1、21分制&#xff0c;三局两胜为佳 2、每球得分制 3、每回合中取胜的一方的一分 4、双方均为20分时&#xff0c;领先对方2分一方获胜 5、双方均为29分时&#xff0c;先到达30分一方获胜 6、一局比赛中获胜方在下一局率先开球 代码如下&#xff1a;…...

株洲市住房和城乡建设局网站/深圳市网络seo推广平台

#function:将AC与GLPI数据库的MAC同步#exception.mac单独存放于GLPI数据库&#xff0c;为以后GLPI二次开发做准备,mac,user,model,approval#date: 2015.12.01邮件显示MAC属性信息&#xff0c;简化代码#date: 2015.12.02修改部分NULL问题&#xff0c;练习left join on 多表联合查…...

wordpress+电脑微信登陆不了/太原网站开发

我们前面做了那么多准备不是白做的&#xff0c;大家如果坚持到现在&#xff0c;真的值得给自己一个拥抱&#xff01;现在我们就来开始着手处理后台管理系统。 首先&#xff0c;大家需要整合淘淘商城的后台管理系统静态页面&#xff0c;即需要将以下css、js、jsp三个文件夹添加到…...