普通视图
Next.js第十三章(缓存组件)
“受控组件”的诅咒:为什么你需要 React Hook Form + Zod 来拯救你的键盘?
将flutter打成aar包嵌入到安卓
Flutter Module 打包成 AAR 并集成到 Android 项目
一、创建 Flutter Module
如果你 还没有 Flutter Module
flutter create -t module flutter_module
二、构建 Flutter AAR
执行 AAR 构建命令
flutter build aar
构建产物位置
flutter_module/build/host/outputs/repo/
构建控制台输出
在下面的配置中会用到
1. Open <host>\app\build.gradle
2. Ensure you have the repositories configured, otherwise add them:
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
repositories {
maven {
url 'E:\lumosproject\module\lumos\build\host\outputs\repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
}
3. Make the host app depend on the Flutter module:
dependencies {
debugImplementation 'com.example.lumos:flutter_debug:1.0'
profileImplementation 'com.example.lumos:flutter_profile:1.0'
releaseImplementation 'com.example.lumos:flutter_release:1.0'
}
4. Add the `profile` build type:
android {
buildTypes {
profile {
initWith debug
}
}
}
三、Android 宿主项目集成 AAR
1. 修改MyApp/app/build.gradle
你目前现有的 Android 项目可能支持 mips 或 x86 之类的架构,然而,Flutter 当前仅支持 为 x86_64,armeabi-v7a 和 arm64-v8a 构建预编(AOT)的库。
可以考虑使用 abiFilters 这个 Android Gradle 插件 API 来指定 APK 中支持的架构,从而避免 libflutter.so 无法生成而导致应用运行时崩溃,具体操作如下:
Groovy 版
android {
defaultConfig {
ndk {
// Filter for architectures supported by Flutter
abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
}
}
}
Kotlin DSL
android {
defaultConfig {
ndk {
// Filter for architectures supported by Flutter
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
}
}
}
2. 配置 settings.gradle(.kts)
在国内,需要使用镜像站点代替 storage.googleapis.com。有关镜像的详细信息,参见 在中国网络环境下使用 Flutter 页面。
Groovy 版
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.flutter-io.cn"
repositories {
google()
mavenCentral()
maven {
url 'E:/lumosproject/module/lumos/build/host/outputs/repo'
}
maven {
url "$storageUrl/download.flutter.io"
}
}
}
Kotlin DSL
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven("https://storage.googleapis.com/download.flutter.io")
}
}
3. 添加依赖(app/build.gradle)
这里添加的依赖都是build aar 控制台输出的内容
groovy版
dependencies {
debugImplementation 'com.example.untitled:flutter_debug:1.0'
profileImplementation 'com.example.untitled:flutter_profile:1.0'
releaseImplementation 'com.example.untitled:flutter_release:1.0'
}
Kotlin DSL
dependencies {
debugImplementation("com.example.flutter_module:flutter_debug:1.0")
releaseImplementation("com.example.flutter_module:flutter_release:1.0")
add("profileImplementation", "com.example.flutter_module:flutter_profile:1.0")
}
4. 添加profile build type(app/build.gradle)
在buildTypes中添加
groovy版
profile {
initWith debug
}
Kotlin DSL
create("profile") { initWith(getByName("debug")) }
四、Android 启动 Flutter 页面
1.在 AndroidManifest.xml 中添加 FlutterActivity
Flutter 提供了 FlutterActivity,用于在 Android 应用内部展示一个 Flutter 的交互界面。和其他的 Activity 一样,FlutterActivity 必须在项目的 AndroidManifest.xml 文件中注册。将下边的 XML 代码添加到你的 AndroidManifest.xml 文件中的 application 标签内
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
/>
2. 加载 FlutterActivity
确保使用如下的语句导入:
import io.flutter.embedding.android.FlutterActivity;
MyButton(onClick = {
startActivity(
FlutterActivity.createDefaultIntent(this)
)
})
@Composable
fun MyButton(onClick: () -> Unit) {
Button(onClick = onClick) {
Text("Launch Flutter!")
}
}
上述的例子假定了你的 Dart 代码入口是调用 main(),并且你的 Flutter 初始路由是 '/'。 Dart 代码入口不能通过 Intent 改变,但是初始路由可以通过 Intent 来修改。下面的例子讲解了如何打开一个自定义 Flutter 初始路由的 FlutterActivity。
MyButton(onClick = {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(this)
)
})
@Composable
fun MyButton(onClick: () -> Unit) {
Button(onClick = onClick) {
Text("Launch Flutter!")
}
}
3. 使用缓存 Engine(推荐)
每一个 FlutterActivity 默认会创建它自己的 FlutterEngine。每一个 FlutterEngine 会有一个明显的预热时间。这意味着加载一个标准的 FlutterActivity 时,在你的 Flutter 交互页面可见之前会有一个短暂的延迟。想要最小化这个延迟时间,你可以在抵达你的 FlutterActivity 之前,初始化一个 FlutterEngine,然后使用这个已经预热好的 FlutterEngine。
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// Cache the FlutterEngine to be used by FlutterActivity.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
使用
myButton.setOnClickListener {
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(this)
)
}
为缓存的 FlutterEngine 设置初始路由
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Configure an initial route.
flutterEngine.navigationChannel.setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
通过设置路由的方式可以配置缓存引擎在执行 Dart 入口点之前使用自定义初始路由
五、Flutter 与 Android 通信
MethodChannel方式
Flutter
添加到合适的地方
static const methodChannel = MethodChannel('com.bluetoothCharacteristic');
methodChannel.setMethodCallHandler(_handleMethod);
Future<dynamic> _handleMethod(MethodCall call) async {
switch (call.method) {
case 'bluetoothCharacteristic':
// 设置设备写入特征
_device.setWriteCharacteristic(
call.arguments as BluetoothCharacteristic,
);
break;
default:
throw PlatformException(code: 'Unrecognized Method');
}
}
Android
private lateinit var methodChannel: MethodChannel
methodChannel = MethodChannel(
flutterEngine.dartExecutor.binaryMessenger,
"com.bluetoothCharacteristic"
)
使用
Button(
onClick = {
val intent = FlutterActivity
.withCachedEngine("my_engine_id")
.build(activity)
methodChannel.invokeMethod(
"bluetoothCharacteristic",
"00002a37-0000-1000-8000-00805f9b34fb"
);
activity.startActivity(intent)
},
modifier = Modifier.padding(16.dp)
) {
Text("跳转到flutter页面")
}
MethodChannel 中的 com.bluetoothCharacteristic 和methodChannel.invokeMethod中的bluetoothCharacterstic必须和 Flutter 中保持一致,否则接收不到数据。
EventChannel方式
EventChannel的使用方式大致和MethodChannel相同,我这里就把主要代码复制下来了。
class MainActivity : ComponentActivity() {
private lateinit var flutterEngine: FlutterEngine
private lateinit var eventChannel: EventChannel
private var eventDataSink: EventChannel.EventSink? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// 创建 FlutterEngine
flutterEngine = FlutterEngine(this)
//设置初始路由
flutterEngine.navigationChannel.setInitialRoute("/settings")
// 启动 Flutter 引擎
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// 设置 EventChannel 用于实时数据传输
eventChannel = EventChannel(flutterEngine.dartExecutor, "com.example.flutter_aar_demo/event_channel")
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventDataSink = events
startRealTimeDataTransmission()
}
override fun onCancel(arguments: Any?) {
eventDataSink = null
}
})
// 缓存 FlutterEngine
FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
}
我这里的安卓使用的是groovy的创建方式Kotlin DSL的方式是我直接从官网复制的代码可能有不正确的地方具体请查看将 Flutter module 集成到 Android 项目。
拿捏 React 组件通讯:从父子到跨组件的「传功秘籍」
深入MCP本质——编写自定义MCP Server并通过Cursor调用
React 新手村通关指南:状态、组件与魔法 UI 🧙♂️
Vue3 服务端渲染 (SSR) 深度解析:从原理到实践的完整指南
Vue3 事件修饰符深度解析:从基础到高级应用的完整指南
JavaScript性能与优化:手写实现关键优化技术
解放双手!使用Cursor+Figma MCP 高效还原响应式设计稿
Vue3 组件懒加载深度解析:从原理到极致优化的完整指南
平面几何:如何绘制一个星形?
Vue的Class绑定对象语法如何让动态类名切换变得直观高效?
Day01-APIs
1.变量声明
1.优先选择const
- 建议数组和对象都用const来声明
1.1 引用数据类型修改仍可用const
只要地址不修改,它也不会报错
<script>
1.数组即使追加也可以定义成const
因为数组地址没变
const arr = ['莎莎','vv']
arr.push('鑫鑫')
console.log(arr);
下面这样会报错,因为这样子是开辟了一个新地址,并且赋给了arr
arr = [1,2,3]
console.log(arr);
</script>
2.API作用与分类
![]()
3.什么是DOM
Document Object Model----文档对象模型
作用:通过js操作网页内容,实现用户放纵
![]()
4.DOM树
document是DOM提供的一个对象,网页中所有内容都在document里面
5.DOM对象(重要)
html的标签 js获取后就变成了对象
核心:把内容当对象处理
6.获取DOM对象
1.根据CSS选择器来获取DOM元素(重点)
2.其他获取DOM元素的方法(了解)
6.1 利用css选择器来获取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
}
#nav {
background-color: pink;
}
</style>
</head>
<body>
<div class="box">123</div>
<div class="box">456</div>
<p id="nav">导航栏</p>
<ul>
<li>啦啦啦1</li>
<li>啦啦啦2</li>
<li>啦啦啦3</li>
</ul>
<script>
// 1. 获取匹配的第一个元素
const box1 = document.querySelector('div')
console.log(box1)
const box2 = document.querySelector('box')
console.log(box2)
// id选择器一定要加#号
const nav = document.querySelector('#nav')
nav.style.background = 'green'
console.log(nav);
// 获取第一个li
const li = document.querySelector('ul li:first-child')
console.log(li);
//2.选择所有的小li
const lis = document.querySelectorAll('ul li')
console.log(lis);
</script>
</body>
</html>
- 遍历得到的伪数组
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.box {
width: 200px;
height: 200px;
}
#nav {
background-color: pink;
}
</style>
</head>
<body>
<div class="box">123</div>
<div class="box">456</div>
<p id="nav">导航栏</p>
<ul class="nav">
<li>啦啦啦1</li>
<li>啦啦啦2</li>
<li>啦啦啦3</li>
</ul>
<script>
// 遍历这个伪数组
const lis = document.querySelectorAll('.nav li')
for(let i = 0; i < lis.length; i++){
console.log(lis[i])
}
</script>
</body>
</html>
6.2 其他方法
![]()
7.操作元素的内容
![]()
7.1 对象.innerText属性
<script>
// 1.获取元素
const box = document.querySelector('.box')
// 2.修改文字内容
console.log(box.innerText)
box.innerText = '我是莎莎'
console.log(box.innerText);
</script>
7.2 对象.innerHTML属性
7.3 年会抽奖案例
<script>
// 1.声明数组
const arr = ['莎莎','vv','鑫鑫','大伟','坤哥']
// 2.随机生成一个数字
for(let i = 0; i < 3; i++){
let random = Math.floor(Math.random()*arr.length)
// 获取这个元素并修改
if(i === 0){
const span = document.querySelector('.wrapper #one')
span.innerText = arr[random]
}else if(i=== 1){
const span = document.querySelector('.wrapper #two')
span.innerText = arr[random]
}else{
const span = document.querySelector('.wrapper #three')
span.innerText = arr[random]
}
arr.splice(random,1)
}
</script>
8.操作元素属性
8.1 操作元素常用属性href、src、title
8.1.1 随机刷新图片案列
<body>
<img src="./images/1.webp" alt="">
<script>
function getRandom(min, max) {
// 先处理边界:如果min > max,交换两者
if (min > max) [min, max] = [max, min];
// 核心公式:Math.floor(Math.random() * (max - min + 1)) + min
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 1.获取图片对象
const img = document.querySelector('img')
// 2.修改图片的src属性
const random = getRandom(1,6)
img.src = `./images/${random}.webp`
img.title = '这就是你啊'
</script>
</body>
8.2 操作元素样式属性
8.2.1 通过style修改
- body的样式就不需要获取了,可以直接使用,因为body是唯一的
2.css中遇到bckground-image这种,用小驼峰解决,写成backgroundImage
<body>
<div class="box"></div>
<script>
// 1.获取元素
const box = document.querySelector('.box')
// 2.修改样式属性,别忘了加单位
box.style.width = '300px'
// 遇到css总-的命名方式,用小驼峰命名法解决
box.style.backgroundColor = 'blue'
// 加边框
box.style.border = '2px solid red'
box.style.borderTop = '5px solid pink'
</script>
</body>
<script>
// 因为body是唯一的,所以不需要获取
function getRandom(min, max) {
// 先处理边界:如果min > max,交换两者
if (min > max) [min, max] = [max, min];
// 核心公式:Math.floor(Math.random() * (max - min + 1)) + min
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const random = getRandom(1,10)
document.body.style.backgroundImage = `url(./images/desktop_${random}.jpg)`
</script>
8.2.2 通过className来修改
好处:简洁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
height: 200px;
width: 200px;
background-color: pink;
}
.nav {
color: red;
}
.box {
width: 300px;
height: 300px;
background-color: skyblue;
margin: 20px auto;
padding: 10px;
border: 1px solid #000;
}
</style>
</head>
<body>
<div class="nav">可爱莎莎</div>
<script>
// 1.获取元素
const div = document.querySelector('div')
// 2.添加类名,并且会覆盖前面的类型
div.className = 'box'
// 3.如果想保留之前的类名,可以使用下面的方法
div.className = 'nav box'
</script>
</body>
</html>
8.2.3 通过classList操作类控制css
这个是用的最多的
8.2.4 随机切换轮播图
<!DOCTYPE html>
<html lang="en">
<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>轮播图点击切换</title>
<style>
* {
box-sizing: border-box;
}
.slider {
width: 560px;
height: 400px;
overflow: hidden;
}
.slider-wrapper {
width: 100%;
height: 320px;
}
.slider-wrapper img {
width: 100%;
height: 100%;
display: block;
}
.slider-footer {
height: 80px;
background-color: rgb(100, 67, 68);
padding: 12px 12px 0 12px;
position: relative;
}
.slider-footer .toggle {
position: absolute;
right: 0;
top: 12px;
display: flex;
}
.slider-footer .toggle button {
margin-right: 12px;
width: 28px;
height: 28px;
appearance: none;
border: none;
background: rgba(255, 255, 255, 0.1);
color: #fff;
border-radius: 4px;
cursor: pointer;
}
.slider-footer .toggle button:hover {
background: rgba(255, 255, 255, 0.2);
}
.slider-footer p {
margin: 0;
color: #fff;
font-size: 18px;
margin-bottom: 10px;
}
.slider-indicator {
margin: 0;
padding: 0;
list-style: none;
display: flex;
align-items: center;
}
.slider-indicator li {
width: 8px;
height: 8px;
margin: 4px;
border-radius: 50%;
background: #fff;
opacity: 0.4;
cursor: pointer;
}
.slider-indicator li.active {
width: 12px;
height: 12px;
opacity: 1;
}
</style>
</head>
<body>
<div class="slider">
<div class="slider-wrapper">
<img src="./images/slider01.jpg" alt="" />
</div>
<div class="slider-footer">
<p>对人类来说会不会太超前了?</p>
<ul class="slider-indicator">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<div class="toggle">
<button class="prev"><</button>
<button class="next">></button>
</div>
</div>
</div>
<script>
// 1. 初始数据,这是一个数组对象
const sliderData = [
{ url: './images/slider01.jpg', title: '对人类来说会不会太超前了?', color: 'rgb(100, 67, 68)' },
{ url: './images/slider02.jpg', title: '开启剑与雪的黑暗传说!', color: 'rgb(43, 35, 26)' },
{ url: './images/slider03.jpg', title: '真正的jo厨出现了!', color: 'rgb(36, 31, 33)' },
{ url: './images/slider04.jpg', title: '李玉刚:让世界通过B站看到东方大国文化', color: 'rgb(139, 98, 66)' },
{ url: './images/slider05.jpg', title: '快来分享你的寒假日常吧~', color: 'rgb(67, 90, 92)' },
{ url: './images/slider06.jpg', title: '哔哩哔哩小年YEAH', color: 'rgb(166, 131, 143)' },
{ url: './images/slider07.jpg', title: '一站式解决你的电脑配置问题!!!', color: 'rgb(53, 29, 25)' },
{ url: './images/slider08.jpg', title: '谁不想和小猫咪贴贴呢!', color: 'rgb(99, 72, 114)' },
]
// 2.需要一个随机数
const random = Math.floor(Math.random() * sliderData.length)
// 3.获取图片
const img = document.querySelector('.slider-wrapper img')
// 4.修改图片路径
img.src = sliderData[random].url
// 5.获取文字
const text = document.querySelector('.slider-footer p')
// 6.修改文字内容
text.innerHTML = sliderData[random].title
// 7.修改底部颜色,括号里面要写css选择器
const footer = document.querySelector('.slider-footer')
footer.style.backgroundColor = sliderData[random].color
// 8.修改底部小圆点高亮特效
const li = document.querySelector(`.slider-indicator li:nth-child(${random+1})`)
li.classList.add('active')
</script>
</body>
</html>
8.3 操作表单元素属性
![]()
![]()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" value="电脑">
<input type="checkbox" name="" id="">
<script>
// 1.获取元素
const uname = document.querySelector('input')
// 2.获取表单里面的值 用的是表单对象.value
console.log(uname.value)
// 3.修改值
uname.value = '我是可爱莎莎'
uname.type = 'password'
// 给复选框直接加上对号
// 1.获取对象
const input = document.querySelector(input:nth(2)-child)
input.checked = true
</script>
</body>
</html>
8.4 自定义属性
![]()
<body>
<div data-id="1">1</div>
<div data-id="2">2</div>
<div data-id="3">3</div>
<div data-id="4">4</div>
<div data-id="5">5</div>
<script>
const one = document.querySelector('div')
console.log(one.dataset); // 可以输出自定义data属性的集合对象
console.log(one.dataset.id); // 输出1
</script>
</body>
9.定时器-间歇函数
![]()
9.1 开启和关闭定时器
语法:setInterval(函数,间隔时间)和 clearInterval(定时器序号) // 单位是毫秒
也可以直接用匿名函数
返回的这个id代表了开启的定时器的个数
![]()
<script>
//setInterval(函数,间隔时间ms)
// 1.使用匿名函数来每隔1s输出一个句子
let m = setInterval(function(){
console.log('1s执行1次')
},1000)
console.log(m);
// 2.使用外部函数
function fn(){
console.log('可爱莎莎')
}
let n = setInterval(fn,1000)
console.log(n);
// 关闭定时器
clearInterval(m)
</script>
9.1 用户倒计时
GIS 数据转换:GDAL 实现将 CSV 转换为 Shp 数据(一)
react beginwork
这是一个非常核心且深入的问题。beginWork 是 React Fiber 架构中最关键的函数之一。
简单来说,beginWork 是 React Render 阶段(协调阶段) 的“入场口”。React 在遍历 Fiber 树时,每到达一个节点,就会调用一次 beginWork。
它的主要任务是:根据新的 Props 和 State,计算出当前组件应该呈现什么样子(即新的子节点),并将这个新的结构与旧的结构进行对比(Reconciliation/diff),找出差异,最后返回第一个子节点以便继续向下遍历。
下面我们详细拆解 beginWork 所做的事情,最后我会梳理成一张流程图。
beginWork 的核心职责拆解
beginWork 的工作流程可以大致分为三个主要阶段:
阶段一:性能优化探测(Bailout 策略)
在动手做任何真正的渲染工作之前,React 会先尝试“偷懒”。这是 React 高性能的关键所在。
-
检查是否需要更新:
beginWork 会对比当前节点的旧状态 (current fiber) 和新状态 (workInProgress fiber)。它会检查以下几点:
- Props 是否改变? (旧 props !== 新 props)
- Context 是否改变? (对于使用了 Context 的组件)
-
是否有足够优先级的更新任务? (检查
renderLanes,看当前节点是否有需要在本次渲染中处理的更新)
-
尝试复用 (Bailout):
如果上述检查发现什么都没变,并且当前节点本身没有高优先级的更新任务,React 就会认为这个节点及其子树可能不需要更新。
-
完全跳过 (Full Bailout): 如果连它的子树也没有任何更新任务 (检查
childLanes),那么beginWork会直接返回null。这意味着:“这条分支到此为止,下面都不用看了”。 - 浅层跳过 (Shallow Bailout): 如果当前节点不用更新,但是它的子孙节点有更新任务,React 就不能完全停下。它会克隆当前的子节点,然后返回这个子节点,继续向下走,跳过当前组件的重渲染逻辑(比如不会重新执行函数组件体)。
-
完全跳过 (Full Bailout): 如果连它的子树也没有任何更新任务 (检查
总结:这一阶段的目标是尽量复用旧的 Fiber 节点,避免不必要的计算。
阶段二:根据组件类型执行渲染逻辑(The Switch Statement)
如果无法复用(Bailout 失败),说明当前节点确实需要更新。
beginWork 内部是一个巨大的 switch (workInProgress.tag) 语句。它会根据 Fiber 节点的类型(函数组件、类组件、原生 DOM 节点等),执行不同的处理逻辑。
以下是几种常见类型的处理方式:
-
FunctionComponent (函数组件):
-
执行函数体: 调用你写的组件函数,例如
MyComponent(props)。 -
处理 Hooks: 在执行函数体时,
useState,useEffect等 Hooks 会被按顺序执行,计算出最新的 State。 -
产出结果: 函数的返回值(通常是 JSX 转换成的
React.createElement调用结果),这就是新的子元素 (New Children Elements) 。
-
执行函数体: 调用你写的组件函数,例如
-
ClassComponent (类组件):
-
更新实例: 处理
getDerivedStateFromProps,处理 State 更新队列,更新组件实例的state和props。 -
判断是否更新: 调用
shouldComponentUpdate(如果定义了)。 -
调用 Render: 如果需要更新,调用实例的
render()方法。 -
产出结果:
render()方法的返回值,即新的子元素。
-
更新实例: 处理
-
HostComponent (原生 DOM 节点,如
<div>):- 原生节点本身没有业务逻辑运行。
- 它的主要任务是准备处理它的子节点。React 会查看它的新
childrenprop。 - 注意:原生节点的属性 diff(比如 style 变了)并不在 beginWork 做,而是在 completeWork 阶段做。
-
其他类型 (Fragment, ContextProvider, Suspense 等):
- 各自有特定的处理逻辑,但最终目的都是为了确定新的子元素是什么。例如
ContextProvider会将新的 value 推入 Context 栈。
- 各自有特定的处理逻辑,但最终目的都是为了确定新的子元素是什么。例如
总结:这一阶段的目标是运行组件代码,拿到组件最新的“图纸”(新的 React Elements)。
阶段三:协调子节点 (Reconciliation / Diffing)
这是 beginWork 最核心的一步,也是“Virtual DOM Diff”算法真正发生的地方。
不管阶段二是什么类型的组件,最终都拿到了一组新的子元素 (New Elements) 。现在的任务是把这些新元素和旧的子 Fiber 节点 (Current Child Fibers) 进行对比。
React 调用 reconcileChildren(current, workInProgress, nextChildren) 函数来完成这项工作:
-
对比 (Diffing):
- 它会遍历新的 Elements 数组,并尝试与旧的 Fiber 链表进行匹配。
-
Key 的作用: 优先使用
key进行匹配。如果 key 相同且类型相同,就复用旧的 Fiber 节点。 -
类型对比: 如果 key 不同或者类型不同(比如
<div>变成了<span>),则标记旧节点为删除,创建新节点的 Fiber。
-
创建新的 WIP Fiber:
- 根据对比结果,生成新的 Fiber 结构,连接到
workInProgress.child上。
- 根据对比结果,生成新的 Fiber 结构,连接到
-
标记副作用 (Flags / Side Effects):
- 在创建新 Fiber 的过程中,如果发现需要进行 DOM 操作,就会在新 Fiber 上打上标记(
flags)。 - 例如:新插入的节点打上
Placement标记;需要更新属性的节点打上Update标记;需要删除的旧节点打上Deletion标记,并添加到父节点的副作用列表中。
- 在创建新 Fiber 的过程中,如果发现需要进行 DOM 操作,就会在新 Fiber 上打上标记(
总结:这一阶段生成了新的 Fiber 子树结构,并找出了新旧之间的差异,打上了标记,为 Commit 阶段的 DOM 操作做好了准备。
函数返回
完成协调后,beginWork 的工作就完成了。它会返回 workInProgress.child(即新生成的第一个子 Fiber 节点)。
React 的工作循环会拿到这个返回值,将指针移动到这个子节点上,然后对它再次调用 beginWork,从而实现深度优先遍历(DFS)。
梳理图 (React beginWork 流程图)
这张图梳理了 beginWork 的决策路径。
代码段
graph TD
Start["Start beginWork"] --> Input["输入: current Fiber, workInProgress Fiber, renderLanes"]
Input --> CheckBailout{"1. 性能优化检查<br>Props/Context变了吗?<br>有高优更新吗?"}
CheckBailout -- 否 --> CheckChildLanes{"检查子树 Lanes<br>子孙有更新吗?"}
CheckChildLanes -- 无 --> BailoutEnd["全量 Bailout<br>返回 null, 停止分支遍历"]
CheckChildLanes -- 有 --> CloneChild["浅层 Bailout<br>克隆子节点 Fiber"]
CloneChild --> ReturnChild["结束<br>返回 workInProgress.child<br>继续向下遍历"]
CheckBailout -- 是 --> SwitchTag{"2. 根据 Tag 处理<br>switch WIP.tag"}
SwitchTag -- FunctionComp --> ExecFC["执行函数组件体 <br>运行 Hooks useState等"]
ExecFC --> GetChildrenFC["得到新的 Children Elements"]
SwitchTag -- ClassComp --> UpdateClass["更新 Class 实例<br>处理 State, 调用 render"]
UpdateClass --> GetChildrenCC["得到新的 Children Elements"]
SwitchTag -- HostComp --> ProcessHost["处理原生节点<br>如 div, span"]
ProcessHost --> GetChildrenHost["从 props 获取 Children Elements"]
SwitchTag -- Other --> ProcessOther["处理 Context Fragment 等"]
ProcessOther --> GetChildrenOther["得到新的 Children Elements"]
GetChildrenFC --> Reconcile["3. 协调子节点 Diffing<br>调用 reconcileChildren"]
GetChildrenCC --> Reconcile
GetChildrenHost --> Reconcile
GetChildrenOther --> Reconcile
Reconcile --> Diffing["对比旧子Fibers 和 新子Elements"]
Diffing --> CreateWIP["创建/复用 Fiber<br>构建新的 WIP 子 Fiber 树"]
CreateWIP --> MarkFlags["标记副作用 Flags<br>Placement, Update, Deletion"]
MarkFlags --> ReturnChild
style Start fill:#f9f,stroke:#333,stroke-width:2px
style BailoutEnd fill:#eee,stroke:#333,stroke-dasharray: 5 5
style ReturnChild fill:#cce5ff,stroke:#004085,stroke-width:2px
style Reconcile fill:#d4edda,stroke:#28a745,stroke-width:2px