阅读视图

发现新文章,点击刷新页面。

A股三大指数开盘涨跌不一,新股N瑞尔上市首日高开107.39%

36氪获悉,A股三大指数开盘涨跌不一,沪指高开0.05%,深成指低开0.1%,创业板指低开0.57%;光通信、半导体领涨,帝尔激光、合力泰涨超8%,沃格光电涨超6%;餐饮旅游、工程机械板块跌幅居前,西安旅游跌超8%,桂林旅游跌超2%;新股N瑞尔上市首日高开107.39%。

恒指开盘涨0.17%,恒生科技指数涨0.34%

36氪获悉,恒指开盘涨0.17%,恒生科技指数涨0.34%;GEO概念、半导体领涨,MINIMAX-WP涨超3%,壁仞科技、澜起科技涨超2%;石化、煤炭板块跌幅居前,元亨燃气跌超5%,中国石油股份跌超2%。

Flutist - Flutter 模块化架构管理框架

简单而系统地管理你的 Flutter 模块化结构


简介

随着我们的项目增长,模块化似乎势在必行,而我最近发现了一个比较新的Flutter模块化管理框架——Flutist。它是一个专为 Flutter 应用设计的强大项目管理框架,灵感来源于 iOS 开发生态中的 Tuist[1]。它为管理大型 Flutter 项目提供了一套结构化的方法,具备模块化架构、集中式依赖管理和代码生成能力。

为什么选择 Flutist?

模块化是大型 Flutter 项目的标准方案——独立构建、并行开发、测试隔离,优势显而易见。但随着模块数量增长,管理开销也随之攀升。Flutist 通过自动化消除了这些开销。

类型安全的依赖管理

当模块超过 10 个时,包版本不一致的问题极易出现。在 package.dart 中声明一次版本,flutist generate 会自动生成 flutist_gen.dart,所有模块都能通过 IDE 自动补全和类型检查安全地引用依赖。


    
    
    
  // 1. package.dart — 只声明一次版本
Dependency(name: 'dio', version: '^5.3.0'),
Dependency(name: 'flutter_bloc', version: '^8.1.6'),

// 2. flutist_gen.dart — 由 flutist generate 自动生成
Dependency get dio         => dependencies.firstWhere((d) => d.name == 'dio');
Dependency get flutterBloc => dependencies.firstWhere((d) => d.name == 'flutter_bloc');
Module     get authDomain   => modules.firstWhere((m) => m.name == 'auth_domain');

// 3. project.dart — 类型安全引用(IDE 自动补全 ✅)
Module(
  name: 'auth_data',
  dependencies: [package.dependencies.dio, package.dependencies.flutterBloc],
  modules: [package.modules.authDomain],
),

集中式 pubspec.yaml 管理

每增加一个模块就多一个 pubspec.yaml。升级一个包的版本意味着要手动编辑每个引用它的文件。

Flutist 根据 project.dart 的声明自动同步所有 pubspec.yaml 文件。开发者只需编辑一个文件——project.dart


    
    
    
  $ flutist generate
✓ pubspec.yaml synced: app, auth_domain, auth_data, auth_presentation,
                      product_interface, product_implementation ... (24 total)
✓ all architecture rules passed
✓ done (0.8s)

架构规则自动化

仅靠文档和代码审查很难持续保持架构规则的一致性。在开发压力下,domain 层最终会导入 http,或者一个功能模块直接引用了另一个功能模块的实现。这些违规很难被发现,等到发现时往往已经扩散开来。

Flutist 将架构规则转化为可执行代码。在 strictMode: true(默认值)下,任何违规都会立即终止 generate。曾经只存在于文档中的原则,现在变成了构建关卡。


    
    
    
  $ flutist generate
✗ [B4] auth_domain → auth_data: 检测到反向依赖 — domain 不应依赖 data
   → 请从 auth_domain 中移除 Dio 导入,仅声明 Repository 接口
✗ generate 已终止(strictMode: true

即使是刚加入团队、对架构理解不深的新成员,在违反规则的那一刻也能获得清晰的反馈。架构违规不再依赖人工审查,工具会自动检查。

样板代码自动生成

模块化架构最大的痛点之一就是样板代码。每个新功能都需要创建 interfaceimplementationtestingtestsexample 包,每个包都有自己的 pubspec.yamllib/ 结构和 barrel 文件。

像 BLoC 这样的状态管理模式,每个功能都需要 event、state、BLoC、page 和 widget 文件。使用 flutist createflutist scaffold,一条命令就能生成所有这些内容。


    
    
    
  # 以 micro 类型创建 todos 功能 — 5 个包 + 完整结构自动化
$ flutist create --name todos --path features --options micro
✓ features/todos/todos_interface      已创建
✓ features/todos/todos_implementation 已创建
✓ features/todos/todos_testing        已创建
✓ features/todos/todos_tests          已创建
✓ features/todos/todos_example        已创建

# 使用 BLoC 脚手架生成所有文件
$ flutist scaffold --template bloc --name todos_overview --path features/todos/todos_implementation
✓ todos_overview_bloc.dart  已创建
✓ todos_overview_event.dart 已创建
✓ todos_overview_state.dart 已创建

核心特性

特性 说明
声明式 通过单一的 project.dart 文件声明整个项目结构
单一来源 所有依赖版本通过 package.dart 集中管理
规则即代码 架构违规会立即终止生成过程

安装


    
    
    
  dart pub global activate flutist

前置条件:Flutter SDK,并确保 ~/.pub-cache/bin 已添加到 PATH

快速开始

1. 初始化项目


    
    
    
  cd my_flutter_project
flutist init

Flutist 会根据上下文自动适配:

  • pubspec.yaml:询问是否创建新的 Flutter 项目
  • 存在 pubspec.yaml:询问是新建项目还是迁移现有项目
    • • 新项目:创建 app 模块,添加到工作区,生成 lib/main.dart
    • • 现有项目:仅创建配置文件,保留现有代码结构

2. 创建模块


    
    
    
  # 创建 Clean 架构模块
flutist create --name login --path features --options clean

# 创建 Microfeature 架构模块
flutist create --name network --path packages --options micro

# 创建 Lite 模块
flutist create --name auth --path packages --options lite

# 创建单一包
flutist create --name utils --path core

3. 管理依赖


    
    
    
  # 添加包(自动解析版本)
flutist pub add http bloc flutter_bloc

# 同步依赖到所有模块
flutist generate

4. 从自定义模板生成代码


    
    
    
  # 列出可用模板
flutist scaffold list

# 从模板生成
flutist scaffold feature --name login
flutist scaffold feature --name login --path lib/features

命令一览

命令 描述 用法
init 初始化新项目或现有项目 flutist init
create 创建新模块 flutist create --name <name> --path <path> [--options <type>]
generate 同步依赖并重新生成文件 flutist generate
check 检查架构规则(CI 友好,不修改文件) flutist check
test 并行运行所有模块的测试 flutist test [-m <module>]
scaffold 从模板生成代码 flutist scaffold <template> --name <name>
pub 管理依赖 flutist pub add <package>
graph 可视化模块依赖关系 flutist graph [--format <format>]
help 显示帮助信息 flutist help [command]

核心文件

文件 说明
package.dart 外部包版本和模块名称的单一真实来源,多行格式为解析必需
project.dart 声明模块依赖和模块间关系,由 flutist generate 读取
flutist_gen.dart 自动生成的类型安全访问器,提供 IDE 自动补全支持

项目结构

典型的 Flutist 项目结构:


    
    
    
  my_project/
├── project.dart              # 项目配置
├── package.dart              # 集中式依赖管理
├── pubspec.yaml              # 工作区配置
├── lib/                      # 根应用代码
│   └── main.dart
├── app/                      # 主应用模块
│   ├── lib/
│   │   └── app.dart
│   └── pubspec.yaml
├── features/                 # 功能模块
│   └── auth/
│       ├── auth_domain/
│       ├── auth_data/
│       └── auth_presentation/
├── packages/                 # 库模块
│   └── network/
│       ├── network_interface/
│       ├── network_implementation/
│       ├── network_testing/
│       ├── network_tests/
│       └── network_example/
└── flutist/
    ├── templates/            # 脚手架模板
    └── flutist_gen.dart      # 生成的代码

模块类型

flutist create 会生成层级包并自动在 project.dart 中配置依赖关系

Clean 架构 (--options clean)

3 层 Clean Architecture,最适合需要清晰关注点分离的功能模块。


    
    
    
  features/login/
├── login_domain/          # 业务规则、实体、用例(无外部依赖)
├── login_data/            # 仓库、数据源、DTO
└── login_presentation/    # UI 和状态管理

自动配置依赖presentation → domaindata → domain

规则:所有依赖箭头指向 domain,domain 不依赖任何东西。

Microfeature 架构 (--options micro)

5 层 Microfeature Architecture,最适合跨功能共享的可复用库。


    
    
    
  packages/network/
├── network_interface/         # 公共 API(抽象类、模型)
├── network_implementation/    # 具体实现
├── network_testing/           # 测试辅助、模拟对象
├── network_tests/             # 单元测试和集成测试
└── network_example/           # 模块演示应用

自动配置依赖implementation/testing → interfacetests/example → implementation + testing

规则:消费者只依赖 interface,组合根注入实现。

Lite 架构 (--options lite)

4 层 Microfeature lite(无 example),最适合内部 API。


    
    
    
  packages/auth/
├── auth_interface/
├── auth_implementation/
├── auth_testing/
└── auth_tests/

单一包(省略 --options

无层级,最适合工具类、共享模型或应用外壳。


    
    
    
  core/utils/
├── lib/
│   └── utils.dart
└── pubspec.yaml

架构验证

flutist generateflutist check 自动执行以下规则:

规则 说明
实现引用 只有组合根(默认:app)和同功能测试/example 可以引用 _implementation
测试层隔离 _testing 包被排除在生产依赖之外
Example 独立性 _example 模块不能被任何生产代码引用
方向强制 同功能层级遵循声明的依赖方向
循环依赖 通过 DFS 遍历检测,绝不允许

配置选项


    
    
    
  // project.dart
ProjectOptions(
  strictModetrue,              // true(默认):违规时终止 / false:仅警告
  compositionRoots: ['app'],     // 允许引用 _implementation 的模块
)

Scaffold 脚手架模板

将重复性工作保存为模板,通过 flutist scaffold 自动化生成代码。

模板变量

.template 文件和 path 值中使用 {{变量}} 进行替换:

变量 输入 login_feature 输出
`{{name snake_case}}` login_feature login_feature
`{{name pascal_case}}` login_feature LoginFeature
`{{name camel_case}}` login_feature loginFeature
`{{name upper_case}}` login_feature LOGIN_FEATURE

template.yaml 结构


    
    
    
  description: "BLoC Feature Template"

attributes:
  - name: name
    required: true
  - name: path
    required: false
    default: "lib/features"

items:
  - type: file
    path"{{path}}/{{name | snake_case}}/{{name | snake_case}}_bloc.dart"
    templatePath: "bloc.dart.template"

  - typestring
    path"{{path}}/{{name | snake_case}}/README.md"
    contents: |
      # {{name | pascal_case}}

Item 类型

类型 说明
file 读取 .template 文件,替换变量后生成
string 使用内联内容直接生成文件
directory 复制整个模板目录

实战示例:BLoC Feature 模板

bloc.dart.template


    
    
    
  import 'package:bloc/bloc.dart';

part '{{name | snake_case}}_event.dart';
part '{{name | snake_case}}_state.dart';

class {{name | pascal_case}}Bloc
    extends Bloc<{{name | pascal_case}}Event, {{name | pascal_case}}State> {
  {{name | pascal_case}}Bloc() : super(const {{name | pascal_case}}Initial()) {
    on<{{name | pascal_case}}Started>(_onStarted);
  }

  Future<void_onStarted(
    {{name | pascal_case}}Started event,
    Emitter<{{name | pascal_case}}State> emit,
  ) async {}
}

event.dart.template


    
    
    
  part of '{{name | snake_case}}_bloc.dart';

sealed class {{name | pascal_case}}Event {
  const {{name | pascal_case}}Event();
}

final class {{name | pascal_case}}Started extends {{name | pascal_case}}Event {
  const {{name | pascal_case}}Started();
}

state.dart.template


    
    
    
  part of '{{name | snake_case}}_bloc.dart';

sealed class {{name | pascal_case}}State {
  const {{name | pascal_case}}State();
}

final class {{name | pascal_case}}Initial extends {{name | pascal_case}}State {
  const {{name | pascal_case}}Initial();
}

final class {{name | pascal_case}}Loading extends {{name | pascal_case}}State {
  const {{name | pascal_case}}Loading();
}

final class {{name | pascal_case}}Loaded<T> extends {{name | pascal_case}}State {
  final T data;
  const {{name | pascal_case}}Loaded(this.data);
}

final class {{name | pascal_case}}Error extends {{name | pascal_case}}State {
  final String message;
  const {{name | pascal_case}}Error(this.message);
}

运行生成


    
    
    
  $ flutist scaffold bloc_feature --name login --path lib/features

✓ lib/features/login/login_bloc.dart
✓ lib/features/login/login_event.dart
✓ lib/features/login/login_state.dart

实战示例:Riverpod Notifier 模板

notifier.dart.template


    
    
    
  import 'package:riverpod_annotation/riverpod_annotation.dart';
import '{{name | snake_case}}_state.dart';

part '{{name | snake_case}}_notifier.g.dart';

@riverpod
class {{name | pascal_case}}Notifier extends _${{name | pascal_case}}Notifier {
  @override
  {{name | pascal_case}}State build() => const {{name | pascal_case}}State.initial();

  Future<void> load() async {
    state = const {{name | pascal_case}}State.loading();
    try {
      state = const {{name | pascal_case}}State.loaded(null);
    } catch (e) {
      state = {{name | pascal_case}}State.error(e.toString());
    }
  }
}

state.dart.template


    
    
    
  import 'package:freezed_annotation/freezed_annotation.dart';

part '{{name | snake_case}}_state.freezed.dart';

@freezed
class {{name | pascal_case}}State with _${{name | pascal_case}}State {
  const factory {{name | pascal_case}}State.initial()            = _Initial;
  const factory {{name | pascal_case}}State.loading()            = _Loading;
  const factory {{name | pascal_case}}State.loaded(dynamic data) = _Loaded;
  const factory {{name | pascal_case}}State.error(String msg)    = _Error;
}

示例项目

Clean Architecture 示例

flutist_clean_architecture[2]

  • • Domain、Data、Presentation 三层 Clean Architecture
  • • 集中式依赖管理
  • • 大型 Flutter 应用最佳实践

Microfeature Architecture 示例

flutist_microfeature_architecture[3]

  • • Interface、Implementation、Tests、Testing 四层 Microfeature 架构
  • • 完全隔离的可复用库模块
  • • 集中式依赖管理

相关链接

资源 链接
📦 pub.dev pub.dev/packages/fl…
📖 文档网站 deepwiki.com/seonwooke/f…
💻 GitHub github.com/seonwooke/f…

引用链接

[1] Tuist: tuist.io/
[2] flutist_clean_architecture: github.com/seonwooke/f…
[3] flutist_microfeature_architecture: github.com/seonwooke/f…

华泰证券:关注新能源链、出口链等拥挤度不高的景气方向

36氪获悉,华泰证券研报指出,上周A股全面回暖,主要指数反弹拉升,科技成长方向在一季报业绩、创业板改革和美伊谈判缓和的多重催化下领涨。展望短期,市场走势或面临一定扰动,一方面两周停火窗口将于下周到期、美联储主席提名人沃什听证会召开,全球风险偏好存在阶段性回落可能,另一方面A股估值分化系数来到历史高位区间,抱团品种获利盘兑现压力上升,风格存在阶段性再平衡的可能。配置上建议控制仓位、在景气线索内部做轮动,算力链产业趋势不变但短期有兑现压力,建议关注新能源链、出口链等拥挤度不高的景气方向。

氢能产业迈向规模化发展阶段,四大行业上市公司加速“跑马圈地”

日前,国家能源局召开能源领域氢能区域试点工作推进会提出,加快培育氢能未来产业。据统计,在9个试点区域中,吉林、内蒙古、广东湛江、湖北武汉、四川攀枝花等地今年政府工作报告中明确规划了2026年及“十五五”时期氢能发展重点。近年来,越来越多的上市公司已先行涉足氢能产业,相关公司已有260多家,主要包括机械设备、电力设备、基础化工和汽车四大行业。近日,福田汽车、中复神鹰、中国石化等公司都透露了在氢能领域的最新业务进展及规划。(上证报)

6.响应式系统比对:通过 Vue3 响应式库写 React 应用

前言

鉴于 Vue3 已经把响应式库进行了独立,也就是 @vue/reactivity,既然 Mobx 也是一个响应式库都可以应用在 React 上,那么 @vue/reactivity 可不可以也应用在 React 上呢?很显然是可以的,社区里也有很多关于这么方面的实践。那么我们这里也提供一个参考 Mobx 实现的版本。

跟 Mobx 对比的话,@vue/reactivity 就相当于 mobx 库,所以我们只需要参考 mobx-react-lite 实现一个 vue-react-lite 即可。

实现 vue-react-lite

我们通过上一篇文章可以知道 Mobx 是通过 mobx-react-lite 实现与 React 进行链接的,其中最重要的函数就是 observer,那么我们也在 vue-react-lite 中实现一个 observer 函数。根据我们前篇所学的知识知道 observer 是一个高阶函数,所以我们初步把 observer 的基础架构搭建出来。

function observer(baseComponent) {
    return (props) => {
        return baseComponent(props)
    }
}

接下来我们知道 Mobx 中是通过 Reaction 这个订阅者中介来实现不同组件函数的代理的,而在 @vue/reactivity 中的跟 Reaction 相同角色的的则是 ReactiveEffect,那么我们就可以通过它来实现我们想要的功能。

代码实现如下:

import { useState, useRef } from "react"
import { ReactiveEffect  } from "@vue/reactivity"
function observer(baseComponent) {
    return (props) => {
        const [, setState] = useState()
        const admRef = useRef(null)
        if (!admRef.current) {
            admRef.current = new ReactiveEffect(() => {
                return baseComponent(props)
            }, () => {
                setState(Symbol())
            })
        }
        const effect = admRef.current
        return effect.run()
    }
}

那么我们就通过 ReactiveEffect 实现了一个跟 mobx-react-lite 中的 observer 一样的功能的函数。

如果大家对 Vue3 的 effect 函数熟悉的话,我们上述 observer 的实现过程跟 Vue3 的 effect 实现很类似的。我们可以回顾一下 Vue3 的 ReactiveEffect 类的功能,它本质是一个订阅者中介,跟 Vue2 的 Watcher 类是一样的角色。ReactiveEffect 的第一个参数就是具体的订阅者函数,而第二个参数则是一个叫 scheduler 的回调函数,在更新的时候如果存在 scheduler 回调函数则执行 scheduler 回调函数,否则执行第一个参数的函数。基于这个原理,我们就在 ReactiveEffect 的第二个参数中设置执行 React 的更新 setState(Symbol()),同时 ReactiveEffect 上存在一个 run 方法,需要通过手动执行进行初始化。

应用 vue-react-lite

那么我们上面通过 ReactiveEffect 实现了 observer 函数,这样我们就可以在 React 中应用 Vue3 的数据响应式库了。下面我们来测试一下:

import { reactive } from "@vue/reactivity";
import { observer } from "./vue-react-lite"

const proxy = reactive({ name: 'Cobyte', secondsPassed: 0 })

const TimerView = observer(({ proxy }) => <span>the content run in `@vue/reactivity` is "Seconds passed: {proxy.secondsPassed}"</span>)

function App() {
  return (
    <TimerView proxy={proxy}></TimerView>
  );
}

setInterval(() => {
  proxy.secondsPassed +=1
}, 1000)

export default App;

打印结果如下:

tutieshi_640x195_5s.gif

我们发现已经成功把 @vue/reactivity 库应用到 React 中了。

根据 Mobx 的启发实现 Vue 数据响应式的 OOP

我们知道 Mobx 的写法是更倾向 OOP 的,同时是严格遵守单向数据流,所以我们也可以在通过 Vue 响应式库提供的 shallowRef API 实现 OOP。

import { reactive, shallowRef } from "@vue/reactivity"
import { observer } from "./vue-react-lite"

class DataService {
  constructor(val) {
    this.r = shallowRef(val)
  }
  get count() {
    return this.r.value
  }
  setCount(val) {
    this.r.value = val
  }
}
const dataService = new DataService(0)
const TimerView = observer(({ proxy }) => <span>the content run in @vue/reactivity is "Seconds passed: {proxy.count}"</span>)

function App() {
  return (
    <TimerView proxy={dataService}></TimerView>
  );
}

setInterval(() => {
  dataService.setCount(Date.now())
}, 1000)

export default App;

但上述方式还是不能堵住别人可以通过直接修改对象的方式更改响应式的值,从而打破单向数据流的规则。

例如下面的例子:

setInterval(() => {
    dataService.r.value = Date.now()
}, 1000)

那么为了堵住这个漏洞,我们可以通过私有变量来解决:

class DataService {
  #r
  constructor(val) {
    this.#r = shallowRef(val)
  }
  get count() {
    return this.#r.value
  }
  setCount(val) {
    this.#r.value = val
  }
}
const dataService = new DataService(0)

这个时候我们就不能通过直接修改对象的方式更改响应式的值了。

setInterval(() => {
    dataService.#r.value = Date.now()
}, 1000)

我们上述这种方式比较适合基本数据类型的情况,如果是引用类型的话,就不太适用了。如果是引用类型我们不可能在上面写那么多属性访问器,我们可以像 Vue2 那样把所有的响应式数据代理到 Vue 的实例对象上,然后可以通过 this 进行访问。

修改如下:

import { shallowRef } from "@vue/reactivity";
class DataService {
  #r
  constructor(val) {
    this.#r = shallowRef(val)
    // 像 Vue2 一样把响应式数据代理到实例对象上
    return new Proxy(this, {
      get(target, key) {
        // 如果是响应式数据就返回响应式数据
        if (target.#r.value[key]) {
          return target.#r.value[key]
        } else {
          // 如果是自身的属性就返回自身属性,例如 setState
          return target[key]
        }
      },
      set(target, key, val) {
        throw new Error('请通过 setState 方法进行更新')
      }
    })
  }
  setState(val) {
    this.#r.value = val
  }
}

const dataService = new DataService({ name: 'Cobyte', date: '2024-03-22', now: { time: 123 } })
const TimerView = observer(({ proxy, now }) => <span>the content run in @vue/reactivity "author: {proxy.name}, the date is: {proxy.date} now is {proxy.now.time}"</span>)

function App() {
  return (
    <TimerView proxy={dataService} now={dataService.now}></TimerView>
  );
}

setInterval(() => {
  dataService.setState({ name: '掘金签约作者', date: '2024年3月22日', now: { time: Date.now() }})
}, 1000)

export default App;

我们通过把响应式数据代理到实例对象上,优化了引用类型的使用方式。

tutieshi_640x284_4s.gif

至此,我们受 Mobx 的启发实现了在 React 中使用 Vue3 的响应式数据库,同时跟 Mobx、Flux、Redux 一样实现单向数据流。不过我们目前采用的是最新的技术私有变量,这个方案目前兼容性并不好,但作为技术交流也可以给大家一个启发。

为什么 Vue 可以通过重新运行组件 render 函数进行更新?

我们在前篇文章通过相对比较简洁的代码实现了 Mobx 的核心原理,同时对比了同时响应式的 Vue 和 Mobx 的最大设计区别,在 Vue 中创建的响应式数据,是可以随意在任何地方通过普通属性访问器进行修改的,但 Mobx 中则不提倡这种可以随意修改 state 的方式,在 Mobx 中希望开发者通过 actions 来改变 state,本质是像 React 那样通过一个函数来修改 state,或者说是遵循 Flux 和 Redux 的单向数据流思想。同时 Mobx 中的订阅者中介 Reaction 和 Vue 中的订阅者中介实现则有比较大的区别,主要是因为 Mobx 主要的设计受 React 的影响,在更新的时候需要特别的设置,而不像 Vue 那样直接重新运行副作用函数就可以了,这个说到底也是因为 React 不是靠依赖追踪来实现响应式的缘故。

那么问题就来了,为什么 Vue 可以通过重新运行组件 render 函数进行更新,而 React 则不行?当然 React 在普通情况下,你在更新的时候是不知道哪个组件函数需要更新,但我们通过 Mobx 就可以实现了依赖收集,就可以知道更新的时候那些组件函数需要重新执行,但即便这样 React 也不能通过重新执行组件函数来实现更新,这是为什么呢?

一个组件要渲染到页面上需要哪些必备条件呢?我们先看看下面的一个 React 应用的渲染例子:

ReactDOM.render(App, document.getElementById("root")

那么从上述的 React 应用渲染的例子我们可以知道,一个组件渲染到页面上是一定要知道渲染到哪个元素容器中的,这一点无论是 React 还是 Vue 都是一样的。如果仅仅只是执行一个组件函数是不能实现渲染的,所以在实现 Mobx 的 Reaction 的时候,不能像 Vue 的订阅者中介那样实现。那么为什么在 Vue 中可以通过重新运行组件 render 函数进行更新呢,或者是直接重新运行组件函数进行更新呢?

这是因为在 Vue 中被收集到订阅者记录变量中的函数,并不是组件的 render 函数,而是一个高阶函数,在高阶函数内部才最后执行组件的 render 函数。我们这里以 Vue3 中的情况进分析,在 Vue3 中最后处理组件 render 函数的地方是在 setupRenderEffect 函数中,下面是 setupRenderEffect 的简洁实现代码结构。

function setupRenderEffect(instance, initialVNode, container, anchor, parentSusp) {
    const componentUpdateFn = () => {
        if (!instance.isMounted) {
            // 初始化走这里
            const subTree = (instance.subTree = renderComponentRoot(instance))
            // 通过 patch 函数进行挂载,第三个参数就要挂载的HTML容器
            patch(
                null,
                subTree,
                container, // 目标挂载点
                anchor,
                instance,
                parentSuspense,
                isSVG
            )
            instance.isMounted = true
        } else {
            // 更新走这里
            // 重新执行组件 render 函数
            const nextTree = renderComponentRoot(instance)
            // 上一次的生成的虚拟DOM为旧的虚拟DOM
            const prevTree = instance.subTree
            instance.subTree = nextTree
            // 更新也是通过 patch 函数进行挂载,也同样需要提供挂载的HTML容器,也就是第三个参数
            patch(
                prevTree,
                nextTree,
                // parent may have changed if it's in a teleport
                hostParentNode(prevTree.el!)!, // 更新的时候也需要提供渲染的目标挂载HTML元素
                // anchor may have changed if it's in a fragment
                getNextHostNode(prevTree),
                instance,
                parentSuspense,
                isSVG
            )
        }
    }
    // 从这我们可以看到被收集的依赖并不是组件的 render 函数,而是一个包装函数 componentUpdateFn
    const effect = (instance.effect = new ReactiveEffect(
      componentUpdateFn,
      () => queueJob(update), // 调度函数 scheduler,最后还是执行 update 方法
      instance.scope // track it in component's effect scope
    ))
    // 初始化的时候需要执行 run 方法
    const update = (instance.update = () => effect.run())
    // 执行
    update()
}

我们从上面的 Vue3 的 setupRenderEffect 的简洁实现代码中可以看到在 Vue 中所谓收集依赖的依赖并不是组件的渲染函数,而是一个包装函数,在包装函数中在初始化和更新阶段都是通过执行组件的 render 函数获得组件的虚拟DOM,然后再通过 patch 函数进行渲染挂载到具体的元素节点下。而在 Vue 的内部中是可以获取到具体需要渲染挂载的元素节点的,而我们在 React 的应用层首先是无法通过组件函数获得需要挂载的元素节点的,其次 React 的更新流程本质上就跟 Vue 这类型通过依赖收集的数据响应式框架不一样。

总结

本文受 Mobx 启发,利用 @vue/reactivity 的 ReactiveEffect 实现了类似 mobx-react-lite 的 observer 高阶函数,成功将 Vue 响应式库集成到 React 中,实现了单向数据流和依赖追踪。同时,通过私有变量和 Proxy 代理优化了 OOP 风格下的响应式数据访问,避免了直接修改状态。最后,从底层机制解释了 Vue 能够直接重新运行组件 render 函数更新,而 React 不能的根本原因:Vue 的依赖收集针对的是包含 patch 挂载逻辑的包装函数,可获取具体渲染容器;React 的更新流程不依赖此类追踪,且组件函数层面无法获取挂载节点。这揭示了两种框架在设计哲学与实现机制上的本质差异。

我是程序员Cobyte,现在已转向研究 AI Agent,欢迎添加 v: icobyte,学习交流 AI Agent 应用开发。

银河证券:消费板块估值处于历史偏低水平,建议关注农林牧渔等方向

36氪获悉,银河证券研报认为,A股市场配置机会方面,关注一:科技创新、自主可控与产业景气的确定性趋势,重点关注业绩景气度较高的核心环节,涉及电力设备、储能、存储、半导体、算力、通信设备等。关注二:受益于PPI同比转正与价格中枢上移的资源板块,包括有色金属、基础化工、石油石化、建筑材料、钢铁等。关注三:地缘冲突反复博弈中的能源及替代性需求主题、防御性板块,关注煤炭、煤化工、新能源、金融、公用事业等。此外,消费板块估值处于历史偏低水平,部分细分领域具备预期修复空间,建议关注农林牧渔、食品饮料、家用电器等方向。

36氪首发|「东犁退休俱乐部」完成亿元级B轮融资,为300万活力银发族打造“线上+线下”社交生活方式

36氪独家获悉,「上海东犁退休俱乐部」近日完成亿元级B轮融资,由恒旭资本领投,德同资本跟投。本轮资金将用于拓展多元化银发服务场景、加强上游供应链合作,以及推动AI技术与数据中台升级。

上海东犁退休俱乐部(以下简称“东犁”)是一家面向退休人群的社交娱乐服务平台,专注为50-80岁活力银发群体提供旅游、聚会、商品、兴趣爱好等综合性服务。

截至目前,东犁已累计服务超300万用户,覆盖江浙沪地区,复购率超过60%。2025年全年,其旅游业务营收超8亿元,银发电商业务预计今年突破3-4亿元。

从电视制片人到银发创业者

东犁创始人吕辰晔有SMG工作背景及日本留学经历。吕辰晔创办东犁,契机源于一次日本考察——他看到上市公司Halmek通过“电台节目+线下沙龙+内容杂志”的模式服务银发族,深受触动。

“我们当时借鉴了Halmek的模式,用电视代替电台,用公众号代替杂志,同时保留了线下沙龙的形式。”吕辰晔告诉36氪。

彼时,国内退休人群的消费以电视购物为主,旅游市场则充斥低价购物团。东犁首先从旅游产品入手,剔除购物和自费环节,重新设计线路,并提升餐饮、住宿标准。

这种“内容先行”的模式迅速建立了用户信任。东犁旗下节目《我们退休啦》在上海都市频道播出期间,收视率大约在1%,覆盖40万人次/天。节目积累的观众逐渐转化为小程序用户,并进一步沉淀至私域。 “电视圈有句话叫‘得阿姨妈妈者得天下’,我的内容受众其实一直没变。”吕辰晔总结。

如今,东犁的内容矩阵已覆盖微信视频号、抖音、小红书等平台,拥有数百个矩阵账号;其小程序的注册用户超过300万,社群月发帖数过万。

回顾创业历程,让吕辰晔印象深刻的一个场景,是一位白发苍苍的老太太,下雨天坐地铁来上课,课后独自戴着老花镜,用单指在键盘上敲打。“她把人生很重要的一段时间用在了你提供的服务上,你会觉得真的应该对他们更好一点。”

长途旅游到高频聚会,构造产品金字塔

2020年前,东犁以出境游和国内长线为主。突如其来的疫情导致长途旅游受阻,团队开始探索周边聚餐、聚会等短频快业务,意外发现退休人群对高频次、高质量、重场景的社交需求远超预期。

如今,东犁已形成清晰的产品金字塔:塔基是高频的本地聚会,向上依次是国内中长线、出境游及、私家团。吕辰晔解释:“退休人群每一周可能聚会一两次,高频带动低频消费,综合复购率就上去了。”

东犁的旅游用户

这一产品结构演变并非事先规划而成,而是由市场需求推着走。“我们起初并未重视聚会业务的营收潜力,但实际运营后发现它成长非常快。”吕辰晔坦言。以聚餐产品为例,国际饭店套餐销量近2万份,全聚德套餐已破4万单,证明了银发族对本地高品质社交的渴求。

线下的“壹天聚乐部是这一逻辑的自然延伸。目前东犁在上海开设了4家壹天聚乐部门店,面积均超过3000平米,主题包括“岁月情怀”“时光照相馆”“环球主题馆”“世界之窗”,用户人均消费100多元,可享受两顿正餐及棋牌、KTV、舞池等全天服务。门店选址紧邻地铁、居民密集区,店内设沉浸式打卡点,满足用户“九宫格发朋友圈”的社交需求。

东犁的线下业态“壹天聚乐部”

盈利方面,首店“壹天聚”(共康店)18个月实现回本。吕辰晔强调,人均百元含两餐的定价属于低客单价,盈利关键在于持续流量输入与较高的复购频次:“我们不是先做壹天聚乐部再做退休俱乐部,而是反过来的。没有持续的流量和品牌口碑积累,单店很难存活。上海乃至全国,可能只有我们的壹天聚乐部能够实现短期内盈利。”

与传统餐饮不同,壹天聚乐部采用套餐制,每三个月换一次菜单,通过集中采购控制成本,减少后厨能耗浪费。同时,利用退休人群时间灵活的特点,填补商场、影院、面包房等业态的客流潮汐时段,形成良性互补的跨业态合作。

在上海模式跑通后,吕辰晔对异地扩张仍然极为谨慎。“壹天聚乐部在外地很难简单复制,除非是重庆、成都这样消费活力强劲、老年人口占比高的城市。”

未来两年,东犁将重点发力两大方向:电商(聚焦视频号及私域)和主题游(不一样系列)。

其中,主题游计划开发500-600条线路,以8-10人小团为主,满足分餐制、深度体验等新兴需求。“如果能把主题游做好,以产品为纲,无论平台怎么变,都会有好结果。”东犁联合创始人李萍介绍到,“公司近期上线的‘不一样的黄山’系列,以超过4000元的客单价,凭借‘全程分餐制+当地特色美食’的餐饮颠覆、‘挖掘小众深度文化体验’的行程设计以及‘住得不一样’的品质住宿,重新定义中老年黄山之旅。线路一经推出,未来3个月班期即售罄,销量破千单。”

从资深内容制作者到银发经济探索者,吕辰晔希望东犁成为“活力退休人群的生活方式平台”。而这场关于“夕阳”的生意,朝阳才刚刚升起。

投资人观点:

作为本轮领投方,恒旭资本高度看好兼具社会价值与商业价值的银发经济赛道。恒旭资本认为,东犁拥有敏锐的行业洞察、专业的内容基因和精细的运营能力,有潜力成为中国银发经济的领导者。恒旭资本于2019年创立,聚焦硬科技、健康消费等符合国家转型和新质生产力要求的投资机遇,累计管理规模已超过400亿元人民币。此次领投东犁,是其在健康消费领域的又一重要布局。

跟投方德同资本十余年来深耕先进制造、医疗健康与AI等核心领域。日前,发起设立十亿规模文化科技基金,联合安徽文旅投控、芜湖高新产业发展基金、杭州文投等共同出资,旨在推动AI与实体经济深度融合,引领科技文化投资前沿。基金首关后迅速布局银发经济,投资了东犁项目。德同资本认为,东犁定义了新一代“旅游-娱乐-社交-消费”的适老化产品与服务,积淀了“信任”护城河,德同将以优势资源助力其加速银发产业升级发展。

机器人ToB规模化提速,数据短板仍是核心卡点

从仓储环节的拆码垛自主决策,到车厂流利架分拣、工程螺栓保护软套剥离,再到药店场景的货架识别、精准抓药打包……近来,机器人正加速向ToB(面向企业)领域渗透,并逐渐成为驱动产业增长的核心力量。不过,业内人士指出,机器人的通用能力才是实现大规模商业化落地的关键所在。当前,大模型的算力与算法发展已日趋成熟,真正制约机器人场景泛化能力的核心卡点,仍是数据短板。对此,业界期盼政策能从开放应用场景、补贴数据建设、降低企业落地风险、打通市场准入等多个维度提供有力支撑,推动机器人更快走进真实生产生活场景。(经济参考报)

中信建投:关注推理算力和商业航天发展机遇

36氪获悉,中信建投证券研报称,AI算力与商业航天迎来产业加速期。算力端,应用演进深刻重构基础设施:Agent推动计算负载从GPU密集到CPU密集,数据中心CPU与GPU配比有望大幅提升;AI算力需求爆发叠加内存涨价以及产能紧张等因素,共同推动今年以来的服务器CPU缺货及涨价;大模型推理降本诉求则驱动巨头加速布局ASIC,行业走向GPU+ASIC的异构协同。航天端,以2026航天日为契机,随着多型可重复使用火箭迎来密集验证,运力供给的提升将全面加速卫星互联网组网,推动商业航天迈入高质量发展阶段。

工信部等部门召开光伏行业座谈会

36氪获悉,4月17日,工业和信息化部、国家发展改革委、市场监管总局、国家能源局等部门联合召开光伏行业座谈会,部署规范光伏产业竞争秩序相关工作。会议强调,要深入贯彻落实党中央、国务院关于规范光伏产业竞争秩序的工作部署,深刻认识治理“内卷式”竞争的重要性和紧迫性,扎实推进光伏行业“反内卷”工作。会议要求,要加强部门协同、同向发力,持续深化光伏产业治理工作,全力推进产能调控、标准引领、创新驱动、价格执法、质量监管、兼并重组、知识产权保护等“反内卷”综合治理有关工作,推动光伏产业高质量发展。

两市融资余额增加21.69亿元

36氪获悉,截至4月17日,上交所融资余额报13414.25亿元,较前一交易日增加6.19亿元;深交所融资余额报13014.22亿元,较前一交易日增加15.5亿元;两市合计26428.47亿元,较前一交易日增加21.69亿元。

美国航空驳斥与美联航的合并谈判

美国航空上周五表示,该公司无意与美国联合航空合并,且没有就此进行过任何谈判,这削弱了这项可能重塑行业格局、但将面临严格监管审查的交易前景。美国航空表示,“尽管更广泛的航空市场可能需要变革,但与美联航的合并将对竞争和消费者产生负面影响。”美国航空并称,此类交易与其对特朗普政府反垄断执法方针的理解不符。美联航拒绝置评,白宫也未立即回应置评请求。白宫此前曾表示,对美联航可能与美国航空合并一事没有意见。(新浪财经)

华泰证券:强业绩线索中,电池、电力链拥挤度不高仍可参与

36氪获悉,华泰证券研报认为,一方面,上周市场面临的海外扰动增多,另一方面,尽管景气投资的有效性、阶段性增强,但通信、算力等核心板块短期或有一定兑现压力。因此,建议控制仓位,并调整持仓,具体来看,第一,算力链业绩兑现程度高但短期筹码兑现压力或较高,静待风险解除后再度参与或更优,中期角度算力产业逻辑仍清晰、当前PEG(未来3年)并不算高,第二,强业绩线索中,电池、电力链(储能、电网设备等)拥挤度不高仍可参与,第三,景气边际改善的品种中出口链占比较高,家用电器、工程机器、光伏组件近期出口增速均有恢复,也可适度关注,第四,红利持仓目前建议以低波类为主。
❌