Kubesphere前端项目分析

1 KubeSphere console功能导图

模块:

  1. 命令行工具 (kubectl)

  2. 日志(Logging)

  3. 平台设置(Platform Settings)

  4. 服务组件(Service Components)

  5. 监控和警报(Monitoring & Alerting)

  6. 基础架构(Infrastructure)

  7. 集群角色(Cluster Roles)

  8. 账户(Accounts)

  9. 应用商店(OpenPitrix App)

  10. 工作区(Workspaces)

  11. 项目(Projects)

  12. (开发运维)Devops

2 KubeSphere console整体结构

2.1 概述

ks-console主要是作为用户和kubereshpere交互的入口,主要为用户提供页面的交互方式,以及少量API接口。 如图所示,ks-console第一层级主要包含会话管理,其他API,页面。

  1. 会话管理主要是登录授权后维持用户token等的权限cache

  2. 其他API主要是直接提供了部分和dockerhub或者下载的部分API

  3. 页面主要提供用户的交互入口

从页面功能来看,又分为管理页面和终端页面,终端页面是提供在页面上使用命令行直接与kubernetes的交互界面,管理页面则是集成化的对整个kubesphere的管理

管理业面又主要分为集群管理,权限管理和系统设置

  • 集群管理 管理集群的整体资源

  • 权限管理 管理用户或用户组的权限

  • 系统设置 对系统邮箱,webhook(消息通知)等全局配置进行管理

通知配置页面

2.2 整体结构分析

  1. 路由层是整个前端系统的入口,主要使用koa(Node.js的服务端开发框架)提供了最外层的服务框架,其中嵌入了配置管理config和部分交互数据压缩等的中间件工具utils。

  2. 会话层主要是提供了用户的登录,以及登录后的session数据维持管理; 主要提供页面的访问入口。此外还有dockerhub等API接口。

  3. 路由分发层则从业面上做了功能分发,提供管理页面(kubesphere pages)以及终端页面(terminal)两个访问入口。

  4. 页面逻辑层中才是管理页面的真正实现,使用react框架,完成了页面的支持。

  5. 管理页面或者终端页面都将最终在后台API层通过ks-apiserver 与后台交互。

  • 路由层 如下所示,可以看到在路由层中,根据访问路径对业务进行分发,包括基本工具API,登录管理API,终端页面,和最后的用户管理页面。

router.use(proxy('/devops_webhook/(.*)', devopsWebhookProxy)).use(proxy('/b2i_download/(.*)', b2iFileProxy)).post('/dockerhub/(.*)', parseBody, handleDockerhubProxy).post('/harbor/(.*)', parseBody, handleHarborProxy).get('/blank_md', renderMarkdown).all('/(k)?api(s)?/(.*)', checkToken, checkIfExist).use(proxy('/(k)?api(s)?/(.*)', k8sResourceProxy)).get('/sample/:app', parseBody, handleSampleData)// session.post('/login', parseBody, handleLogin).get('/login', renderLogin).post('/login/confirm', parseBody, handleLoginConfirm).get('/login/confirm', renderLoginConfirm).post('/logout', handleLogout)// oauth.get('/oauth/redirect/:name', handleOAuthLogin)// terminal.get('/terminal*', renderTerminal)// page entry.all('*', renderView)module.exports = router
  • 路由分发层 注意前面最后路由分发的 renderTerminal 和 renderView其实现如下,该层是根据路由的路径不同,去查询对应打包文件中的页面入口,从而真正让用户进入终端页面和管理业面。

const renderTerminal = async ctx => {try {const manifest = getManifest('terminalEntry')const [user, ksConfig, runtime] = await Promise.all([getCurrentUser(ctx),getKSConfig(),getK8sRuntime(ctx),])const localeManifest = getLocaleManifest()await ctx.render('terminal', {manifest,isDev: global.MODE_DEV,title: clientConfig.title,hostname: ctx.hostname,globals: JSON.stringify({localeManifest,user,ksConfig,runtime,}),})} catch (err) {renderViewErr(ctx, err)}
}
const renderView = async ctx => {try {const clusterRole = await getClusterRole(ctx)const [user, ksConfig, runtime, supportGpuType] = await Promise.all([getCurrentUser(ctx, clusterRole),getKSConfig(),getK8sRuntime(ctx),getSupportGpuList(ctx),])await renderIndex(ctx, {ksConfig,user,runtime,clusterRole,config: { ...clientConfig, supportGpuType },})} catch (err) {renderViewErr(ctx, err)}
}
  • 页面逻辑层 因为终端页面直接使用的第三方库,因此基本没有开发逻辑,而管理页面则是使用react实现后打包完成.

2.3 项目的目录结构

2.3.1 build

里面只有一个Dockerfile 。在Linux环境下打包

2.3.2 cypress和jest 都是测试框架。可以模拟用户与KubeSphere Console进行交互

(1)Cypress是一个用于进行端到端测试的JavaScript测试框架,它允许开发人员编写和运行自动化测试来模拟用户与应用程序的交互。

cypress目录下,通常包含以下文件和子目录:

  • integration目录:该目录用于存放Cypress的集成测试文件。集成测试是指测试应用程序的不同组件之间的交互和协调是否正常。

  • plugins目录:该目录包含Cypress的插件文件。插件文件允许您在运行测试时对Cypress进行自定义配置或执行其他操作。

  • support目录:该目录用于存放支持测试的辅助文件。这些文件包括自定义命令、工具函数或测试配置。

  • fixtures目录:该目录用于存放测试所需的静态资源或测试数据。例如,您可以将一些模拟的JSON文件或图像文件放在这个目录下,供测试使用。

  • screenshots目录:该目录用于存放测试运行过程中自动生成的截图。这些截图可用于调试和分析测试失败的原因。

  • videos目录:该目录用于存放测试运行过程中生成的视频录制。这些视频可用于回放测试的执行过程。

通过使用Cypress框架和编写测试脚本,可以模拟用户与KubeSphere Console进行交互,并验证应用程序的行为和功能是否符合预期。cypress目录提供了组织和管理这些测试文件所需的结构和资源。

(2)Jest是一个流行的JavaScript测试框架,主要用于编写单元测试和集成测试。

jest目录下,通常包含以下文件和子目录:

  • setup.js文件:该文件包含在执行测试之前需要进行的全局设置和配置。您可以在此文件中编写代码来配置测试环境、导入所需的测试库或设置全局变量。

  • test-utils.js文件:该文件通常包含一些测试工具函数,用于辅助编写测试代码。这些工具函数可以包括模拟数据、创建测试环境或执行常见的测试操作。

  • __mocks__目录:该目录用于存放模拟(mock)文件或模块。模拟文件可以用于模拟外部依赖或模块,以便在测试中进行替代或模拟。

  • __tests__目录:该目录用于存放Jest测试文件。在这个目录中,您可以编写单元测试或集成测试,以验证KubeSphere Console中的各个功能和模块的行为是否符合预期。

通过使用Jest测试框架和编写测试脚本,可以对KubeSphere Console的不同部分进行测试,包括组件、函数、API等。jest目录提供了组织和管理这些测试文件所需的结构和资源,并允许您进行测试配置和定制。

2.3.3 hack

通常用于存放一些用于辅助开发、构建和部署的脚本、配置和工具。

具体而言,hack目录的作用可以包括以下内容:

  1. 构建和部署脚本:该目录可能包含一些用于自动化构建和部署KubeSphere Console的脚本。这些脚本可以包括构建Docker镜像、部署到Kubernetes集群或其他云平台的脚本等。

  2. 代码生成器和模板:一些项目可能会使用代码生成器或模板引擎来自动生成一些代码文件或模板文件。这些文件可以位于hack目录中,并用于生成特定的代码结构或文件。

  3. 工具脚本:hack目录可能包含一些用于辅助开发和调试的工具脚本。这些脚本可以执行一些特定的任务,例如数据转换、格式化代码、静态分析、检查依赖等。

  4. 环境配置和样例文件:hack目录可能包含一些用于配置开发环境或提供样例配置文件的文件。这些文件可以包括本地开发环境的配置示例、测试环境的配置文件模板等。

总体而言,hack目录是一个用于存放各种辅助开发、构建和部署的脚本、配置和工具的目录。它提供了一个组织和管理这些辅助文件的位置,使得开发者能够更方便地进行开发、构建和部署相关的操作。

2.3.4 locales

存储语言文件,国际化

2.3.5 scripts

存储语言文件,国际化

2.3.6 server

用于存放与服务器端相关的代码和配置文件。

2.3.7 src

2.3.7.1 actions

通常存放着与应用程序的动作(Actions)相关的代码。在前端应用中,动作是指触发状态变化或触发其他操作的行为。Actions可以被组件、用户交互或其他触发机制调用,它们描述了应用程序中发生的事件或操作。

2.7.3.2 assets

存放一些静态资源,如图片

2.7.3.3 components

存放封装的通用组件

2.7.3.4 configs

配置管理。通过集中定义规则配置项,可以方便地管理和维护规则的相关信息,并在需要时进行扩展和修改。

2.7.3.5 core

核心组件的二次封装,整体入口

2.7.3.6 pages

页面封装

  1)access 访问控制页面

  2) app 应用商店

  3) clusters 集群管理

  4) console 工作台

  5)devops

  6)fedprojects 多集群

  7)Projects 项目

  8)settings 平台设置

  9)terminal 终端页面

  10)workspaces 工作区

2.7.3.7 scss

样式文件

2.7.3.8 stores

页面的数据管理

2.7.3.9 utils

一些工具函数

3 管理页面整体结构分析

  • 首先index是整个页面的入口。

  • index中包含的route则是路由的入口。

  • 路由注册了两种页面,一种是导航页面view1, 一种是逻辑页面view2。 逻辑页面会交互完成集群查询管理,节点管理等具体逻辑功能。而导航页面则只负责展示导航列并提供点击做页面跳转。

  • 导航页面支持动态呈现,其通过global组件从config里面获取页面元素和布局,动态展现支持的资源提供跳转链接。

  • 逻辑页面则是导航页面跳转的。

  • 逻辑页面通过controller调用action中的模块和后台交互,管理获取后台的实际资源。

  • 而store则是在前端存取的后台资源的cache。

  • view展示数据时对应的后台资源则从store获取。

4 React核心概念

4.1 生命周期

很多的事物都有从创建到销毁的整个过程,这个过程称之为是生命周期;

React组件也有自己的生命周期,生命周期可以让我们在最合适的地方完成自己想要的功能

生命周期和生命周期函数的关系:

生命周期是一个 抽象的概念 ,在生命周期的整个过程,分成了很多个阶段

比如装载阶段(Mount ),组件第一次在 DOM 树中被渲染的过程;

比如更新过程(Update ),组件状态发生变化,重新更新渲染的过程

比如卸载过程(Unmount ),组件从 DOM 树中被移除的过程;

React内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的 某些函数进行回调 ,这些函数就是 生命周期函数

比如实现componentDidMount 函数:组件已经挂载到 DOM 上时,就会回调;

比如实现componentDidUpdate 函数:组件已经发生了更新时,就会回调;

比如实现componentWillUnmount 函数:组件即将被移除时,就会回调;

我们可以在这些回调函数中编写自己的逻辑代码,来完成自己的需求功能;

我们谈React 生命周期时,主要谈的类的生命周期,因为函数式组件是没有生命周期函数的

最基础、最常用的生命周期函数

4.1.1 生命周期函数

Constructor

如果不初始化state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

constructor中通常只做两件事情:

(1)通过给this.state 赋值对象来初始化内部的 state

(2)为事件绑定实例(this)

componentDidMount

componentDidMount()会在组件挂载后(插入 DOM 树中)立即调用。

componentDidMount中通常进行哪里操作呢?

(1)依赖于DOM 的操作可以在这里进行;

(2)在此处发送网络请求就最好的地方;(官方建议)

(3)可以在此处添加一些订阅(会在componentWillUnmount 取消订阅);

componentDidUpdate

componentDidUpdate()会在更新后会被立即调用,首次渲染不会执行此方法。

当组件更新后,可以在此处对DOM 进行操作;

如果你对更新前后的props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)。

componentWillUnmount

componentWillUnmount()会在组件卸载及销毁之前直接调用。

(1)在此方法中执行必要的清理操作;

(2)例如,清除timer ,取消网络请求或清除在 componentDidMount() 中创建的订阅等;

4.1.2 不常用生命周期函数

除了上面介绍的生命周期函数之外,还有一些不常用的生命周期函数:

getDerivedStateFromProps:state 的值在任何时候都依赖于 props 时使用;该方法返回一个对象

来更新 state

getSnapshotBeforeUpdate:在 React 更新 DOM之前回调的一个函数,可以获取 DOM 更新前的一

些信息(比如说滚动位置);

shouldComponentUpdate:该生命周期函数很常用(很多时候,我们简称为 SCU ),作为性能优化的一种方式。这个方法接受参数,并且需要有返回值:

该方法有两个参数:

参数一:

nextProps 修改之后,最新的 props 属性

参数二:

nextState 修改之后,最新的 state 属性

该方法返回值是一个boolean 类型:

(1)返回值为true ,那么就需要调用 render 方法;

(2)返回值为false ,那么久不需要调用 render 方法;

(3)默认返回的是true ,也就是只要 state 发生改变,就会调用 render 方法;

4.2 Router路由

react-router 最主要的 API 是给我们提供的一些组件:

BrowserRouter或 HashRouter

(1)Router中包含了对路径改变的监听,并且会将相应的路径传递给子组件;

(2)BrowserRouter使用 history 模式;

(3)HashRouter使用 hash 模式;

4.2.1 路由映射配置

Routes:包裹所有的 Route ,在其中匹配一个路由

Router5.x使用的是 Switch 组件

Route:Route 用于路径的匹配;

(1)path属性:用于设置匹配到的路径;

(2)element属性:设置匹配到路径后,渲染的组件;

Router5.x使用的是 component 属性

(3)exact:精准匹配,只有精准匹配到完全一致的路径,才会渲染对应的组件

Router6.x不再支持该属性

export default [{ path: '/404', component: NotFound, exact: true },{ path: '/dashboard', component: Dashboard, exact: true },{ path: `/logquery`, exact: true, component: LogQuery },{ path: '/eventsearch', exact: true, component: EventSearch },{ path: '/auditingsearch', exact: true, component: AuditingSearch },{ path: '/bill', exact: true, component: Bill },{path: '/',redirect: { from: '/', to: '/dashboard', exact: true },},{path: '*',redirect: { from: '*', to: '/404', exact: true },},
]

4.2.2 路由配置和跳转

Link和 NavLink

(1)通常路径的跳转是使用Link 组件,最终会被渲染成 a 元素;

(2)NavLink是在 Link 基础之上增加了一些样式属性;

(3)to属性: Link 中最重要的属性,用于设置跳转到的路径;

import { Link } from 'react-router-dom'<Link to={`/clusters/${cluster}/components?type=${item.type}`}><Iconname={COMPONENT_ICON_MAP[item.type]}size={44}clickable/>
</Link>
<NavLinkkey={name}className={styles.item}activeClassName={styles.active}to={`${match.url}/${name}`}>{t(title)}</NavLink>

默认的activeClassName

事实上在默认匹配成功时,NavLink 就会添加上一个动态的 active class

所以我们也可以直接编写样式

当然,如果你担心这个class 在其他地方被使用了,出现样式的层叠,也可以自定义 class

4.2.3 路由参数传递

传递参数有二种方式:

(1)动态路由的方式;

(2)search传递参数;

动态路由的概念指的是路由中的路径并不会固定:

比如/detail 的 path 对应一个组件 Detail

如果我们将path 在 Route 匹配时写成 /detail/:id ,那么 / abc 、 /detail/123 都可以匹配到该 Route ,并且进行显示

这个匹配规则,我们就称之为动态路由

通常情况下,使用动态路由可以为路由传递参数

export default [{path: `${PATH}/members/:name`,component: MemberDetail,},{path: `${PATH}/roles/:name`,component: RoleDetail,},{path: `${PATH}/apps/:appId`,component: AppDetail,},{path: `${PATH}/repos/:repo_id`,component: RepoDetail,},
]

search传递参数

import { Link } from 'react-router-dom'<Link to={`/clusters/${cluster}/components?type=${item.type}`}><Iconname={COMPONENT_ICON_MAP[item.type]}size={44}clickable/>
</Link>

4.3 状态管理

参见文章的第六部分:KubeSphere console中React的状态管理工具Mobx

5 KubeSphere console中React组件写法

ks-console中组件采用class组件的写法

export default class UploadInput extends React.Component {static propTypes = {className: PropTypes.string,defaultLogo: PropTypes.string,placeholder: PropTypes.string,value: PropTypes.string,onChange: PropTypes.func,}static defaultProps = {className: '',value: '',onChange() {},}constructor(props) {super(props)this.uploaderProps = {name: 'file',action: '/images/upload',accept: 'image/*',beforeUpload: file => {if (file.size > 1024 * 1024 * 2) {Notify.error(t('FILE_OVERSIZED_TIP'))return false}return true},onSuccess: res => {if (res) {props.onChange(res.path)}},}}render() {const { className, value, placeholder, defaultLogo } = this.propsreturn (<Columns className={classNames('is-variable is-2', className)}><Column className="is-narrow"><imgclassName={classNames(styles.image, 'upload-preview')}src={value || defaultLogo}/></Column><Column><Upload {...this.uploaderProps}><div className={styles.upload}><Icon size={32} name="upload" /><p>{placeholder}</p></div></Upload></Column></Columns>)}
}

React中还可以采用函数式组件的写法


const FileUploader = ({ onFileUpload }) => {const [fileName, setFileName] = useState('');const [fileContent, setFileContent] = useState('');const handleUpload = useCallback((files) => {const reader = new FileReader();const file = files[0];reader.onload = (e) => {const content = e.target.result;setFileContent(content);onFileUpload(file.name, content);};reader.readAsText(file);setFileName(file.name);}, [onFileUpload]);return (<ReactFileReaderfileTypes={['.yaml', '.txt']}handleFiles={handleUpload}><Iconname="upload"size={20}clickablechangeable/></ReactFileReader>);
};export default FileUploader;

5.1 React 的 class 组件和函数组件的区别

相同:都可以接收 props 并返回 react 对象

不同:

  • 编程思想和内存:类组件需要创建实例面向对象编程,它会保存实例,需要一定的内存开销,而函数组件面向函数式编程,可节约内存

  • 可测试性:函数式更利用编写单元测试

  • 捕获特性:函数组件具有值捕获特性(只能得到渲染前的值)

  • 状态:class 组件定义定义状态,函数式需要使用 useState

  • 生命周期:class 组件有完整的生命周期,而函数式组件没有,可以用useEffect 实现类生命周期功能

  • 逻辑复用:类组件通过继承或者高阶组件实现逻辑复用,函数组件通过自定义组件实现复用

  • 跳过更新:类组件可以通过shouldComponents 和 PureComponents(浅比较,深比较可以用immer) 来跳过更新,函数组件react.memo 跳过更新

  • 发展前景:函数组件将成为主流,因为他更好屏蔽this问题,和复用逻辑,更好的适合时间分片和并发渲染

内存开销对比

(1)当使用类组件时,内存开销会具体取决于以下因素:

  1. 组件数量:每个类组件的实例都会占用内存。因此,如果你有大量的类组件实例,它们的内存开销将随之增加。

  2. 组件状态:如果类组件具有较大的状态对象,这些状态数据会占用内存。例如,如果你的类组件包含大型的数据结构或缓存,那么这些数据也会增加内存使用量。

  3. 生命周期方法:每个类组件都具有一组生命周期方法,这些方法本身也会占用一些内存。虽然这一开销通常较小,但仍然需要考虑。

  4. 事件处理函数:如果你在类组件中定义了多个事件处理函数,它们也会占用内存。这包括事件处理函数的引用以及与它们相关的任何闭包变量。

  5. 虚拟DOM:React内部使用虚拟DOM来管理组件的渲染和协调。虚拟DOM的数据结构也需要一定的内存。

综合考虑这些因素,类组件的内存开销可能会因项目的规模、组件数量和每个组件的具体实现而异。对于大型项目,特别是在移动设备上,内存开销可能需要更加谨慎地管理,可以考虑使用函数组件或其他性能优化方法来降低内存开销。

(2)函数式组件相对于类组件通常具有更低的内存开销,这是因为它们不需要创建实例和不涉及类的实例变量。以下是关于函数式组件的内存开销的一些相关方面:

  1. 无实例:函数式组件本质上是纯函数,它们不需要创建类实例,因此不会占用与类实例相关的内存。这意味着你可以拥有大量的函数式组件而不会导致大量的内存开销。

  2. 无生命周期方法:函数式组件通常不包含生命周期方法,因为它们没有类的实例,这减少了内存开销。在函数式组件中,你可以使用React的钩子函数(Hooks)来模拟生命周期行为,但Hooks的实现方式更轻量。

  3. 无实例变量:在函数式组件中,不会有实例变量(例如this.statethis.props)来存储状态或属性,这减少了内存使用。

  4. 适合短期生命周期:函数式组件适用于具有较短生命周期的场景,因为它们在每次渲染时重新执行,不会保留旧的状态。这有助于避免内存泄漏问题。

总之,函数式组件通常在内存效率方面具有优势,因为它们更轻量,不需要创建实例和类的实例变量。这使得它们成为React的首选编程风格,尤其是在需要大量组件并保持较低内存占用的情况下。

5.2 class和Hook的一些对比

Hook是React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期)。

class组件比较常见的是下面的优势:

1 class组件可以定义自己的state,用来保存组件自己内部的状态;

函数式组件不可以,因为函数每次调用都会产生新的临时变量;

2 class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑;

比如在componentDidMount中发送网络请求,并且该生命周期函数只会执行一次;

函数式组件在学习hooks之前,如果在函数中发送网络请求,意味着每次重新渲染都会重新发送一次网络请求;

3 class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等;

函数式组件在重新渲染时,整个函数都会被执行,似乎没有什么地方可以只让它们调用一次;

所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件。

Class组件存在的问题

1 复杂组件变得难以理解:

我们在最初编写一个class组件时,往往逻辑比较简单,并不会非常复杂。但是随着业务的增多,我们的class组件会变得越来越复杂;

比如componentDidMount中,可能就会包含大量的逻辑代码:包括网络请求、一些事件的监听(还需要在componentWillUnmount中移除);

而对于这样的class实际上非常难以拆分:因为它们的逻辑往往混在一起,强行拆分反而会造成过度设计,增加代码的复杂度;

2 难以理解的class:

很多人发现学习ES6的class是学习React的一个障碍。

比如在class中,我们必须搞清楚this的指向到底是谁,所以需要花很多的精力去学习this;

虽然我认为前端开发人员必须掌握this,但是依然处理起来非常麻烦;

3 组件复用状态很难:

在前面为了一些状态的复用我们需要通过高阶组件;

像我们之前学习的redux中connect或者react-router中的withRouter,这些高阶组件设计的目的就是为了状态的复用;

或者类似于Provider、Consumer来共享一些状态,但是多次使用Consumer时,我们的代码就会存在很多嵌套;

这些代码让我们不管是编写和设计上来说,都变得非常困难;

Hook的出现,可以解决上面提到的这些问题;

1 简单总结一下hooks:

它可以让我们在不编写class的情况下使用state以及其他的React特性;

但是我们可以由此延伸出非常多的用法,来让我们前面所提到的问题得到解决;

2 Hook的使用场景:

Hook的出现基本可以代替我们之前所有使用class组件的地方;

但是如果是一个旧的项目,你并不需要直接将所有的代码重构为Hooks,因为它完全向下兼容,你可以渐进式的来使用它;

Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;

3 在我们继续之前,请记住Hook 是:

完全可选的:你无需重写任何已有代码就可以在一些组件中尝试Hook。但是如果你不想,你不必现在就去学习或使用Hook。

100% 向后兼容的:Hook 不包含任何破坏性改动。

6 KubeSphere console中React的状态管理工具Mobx

MobX是一个基于响应式编程的状态管理库,React和MobX是一对强力组合,React提供机制把应用状态转为可渲染组件树并对其进行渲染,而MobX提供机制来存储和更新应用的状态供React使用。MobX背后的哲学很简单:任何源自应用状态的东西都应该自动地获得, 包括UI 数据序列化, 服务器通讯等等

6.1 核心概念

MobX的核心概念有三个:State(状态)、Actions(动作)、Derivations(派生)

6.1.1.定义可观察的State

MobX通过observable标记一个可以被观察的状态并跟踪它们,只需要直接给它们赋值即可实现状态的修改

  • 方法一:显示地标记observable和action

import { makeObservable, observable, action, computed } from "mobx";
​
export class Store {count: number = 0;price = 0;amount = 1;
​constructor() {makeObservable(this, {count: observable, // 标记observableprice: observable,amount: observable,add: action, // 标记action});}
​add() {this.count += 1;}
}
​
  • 方法二:通过makeAutoObservale自动地给类中的每个属性和方法标记上observaleaction

​import { makeAutoObservable, observable, action, computed } from "mobx";
​
export class Store {count: number = 0;price = 0;amount = 1;
​constructor() {makeAutoObservable(this);}
​add() {this.count += 1;}
}
​

6.1.2.使用Action更新State

Action可以理解为任何可以改变State的代码,比如用户事件处理,后端推送数据处理等等 在上面的例子中,add方法改变了count的属性值,而count是被标记为observale的,MobX推荐我们将所有修改observale的值的代码标记为action

6.1.3.创建Derivations以便自动对State变化进行响应

任何来源是State且不需要进一步交互的东西都是Derivations

MobX区分了两种Derivations:

  • Computed:计算属性,可以用纯函数的形式从当前可观测的State中派生

  • Reactions:当State改变时需要运行的副作用

注:副作用可以看成是响应式编程和命令式编程之间的桥梁

  • 通过computed对派生值进行建模

import { makeAutoObservable } from "mobx";
​
export class Store {count: number = 0;price = 0;amount = 1;
​constructor() {makeAutoObservable(this);}
​add() {this.count += 1;}get total() {console.log("computed render");return this.price + this.amount;}// computed可以有setter方法set total(value: number) {this.price = value;}
}

6.2.MobX配合MobX-React创建状态管理

6.2.1.创建Store

1、@observable 定义变量; 2、@action 定义方法;

export default class RootStore {@observablenavs = globals.config.navs@observableshowGlobalNav = false@observableactions = {}@observableoauthServers = []constructor() {this.websocket = new WebSocketStore()this.user = new UserStore()this.routing = new RouterStore()this.routing.query = this.queryglobal.navigateTo = this.routing.push}register(name, store) {extendObservable(this, { [name]: store })}query = (params = {}, refresh = false) => {const { pathname, search } = this.routing.locationconst currentParams = parse(search.slice(1))const newParams = refresh ? params : { ...currentParams, ...params }this.routing.push(`${pathname}?${getQueryString(newParams)}`)}@actiontoggleGlobalNav = () => {this.showGlobalNav = !this.showGlobalNav}@actionhideGlobalNav = () => {this.showGlobalNav = false}@actionregisterActions = actions => {extendObservable(this.actions, actions)}@actiontriggerAction(id, ...rest) {this.actions[id] && this.actions[id].on(...rest)}login(params) {return request.post('login', params)}@actionasync logout() {const res = await request.post('logout')const url = get(res, 'data.url')if (url) {window.location.href = url}}@actiongetRules(params) {return this.user.fetchRules({ ...params, name: globals.user.username })}
}

6.2.2 使用store

方式一

1 在入口文件引入

// mobx
import { Provider } from 'mobx-react';
import store from 'store/index';ReactDom.render(<AppContainer><Provider {...store}><RootElement />      </Provider>    </AppContainer>, document.getElementById('app')  );

2 通过@inject注入,通过 this.props.store名称.方法/变量 的方式使用

import React from 'react';
import { observer, inject } from 'mobx-react';
@inject('UI') 和redux的connect作用一样,将数据注册到组件。
@observer 将你的组件变成响应式组件。就是数据改变时候可以出发重新的渲染。
class LeftMenu extends React.Component {constructor(props) {super(props);this.state = {};}toggleCollapsed = () => {this.props.UI.toggleCollapsed();};render() {const { collapsed } = this.props.UI;return (<div className={cx({ 'left-menu': true, 'left-menu-collapsed': collapsed === true })}><div className="open-menu" onClick={this.toggleCollapsed}>{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined)}</div><Menu mode="inline" theme="dark" inlineCollapsed={collapsed} inlineIndent={15}></Menu></div>);}
}
export default LeftMenu;
方式二

在组件中导入store,构造一个store实例

import RootStore from 'stores/root'import { lazy } from 'utils'const getActions = lazy(() =>import(/* webpackChunkName: "actions" */ 'actions')
)export default class Environments extends React.Component {rootStore = new RootStore()envError = ''static defaultProps = {prefix: '',checkable: true,}get prefix() {const { prefix } = this.propsreturn prefix ? `${prefix}.` : ''}componentDidMount() {getActions().then(actions =>this.rootStore.registerActions(actions.default))}handleErrorStatus = (err = '') => {this.envError = err}envValidator = (rule, value, callback) => {if (this.envError === '') {callback()}}render() {const {checkable,namespace,isFederated,cluster,projectDetail,} = this.propsreturn (<Form.Grouplabel={t('ENVIRONMENT_VARIABLE_PL')}desc={t('CONTAINER_ENVIRONMENT_DESC')}checkable={checkable}><Form.Item rules={[{ validator: this.envValidator }]}><EnvironmentInputrootStore={this.rootStore}name={`${this.prefix}env`}namespace={namespace}isFederated={isFederated}cluster={cluster}projectDetail={projectDetail}handleInputError={this.handleErrorStatus}/></Form.Item></Form.Group>)}
}

7 kubeSphere的官方API接口文档

https://kubesphere.io/api/kubesphere#tag/DevOps-Pipeline/operation/CheckCron

8 实际开发中的一些问题

(1)ks-console中使用class组件写法,许多方法并不是在当前class组件中定义的,而是通过不断继承父组件。导出时,使用的默认导出(意味着在某个组件中使用时,可以重命名),重命名之后相关的方法就比较难以找到(多个组件中可能使用同一个方法名,全局搜索有时会搜索出很多同名方法),这对于二次开发有点困难。

(2)class组件都是继承自 React.Component,而不是继承自PureComponent,这样可能导致一些性能问题。

例如,初始组件中定义一个名为counter的state,初始值为1;当在某处使用setState将counter重置为1,这时该组件就会重新执行render函数,这是我们不希望的。PureComponent可以解决这一问题。

(3)连接后端服务器时,建议在console\server 目录下创建一个local_config.yaml文件,项目启动后,就会从该配置文件读取要连接的后端服务地址和端口号。

local_config.yaml文件内容如下

server:apiServer:url: http://x.x.x.x:port   // 后端服务器地址wsUrl: ws://x.x.x.x:port    // 后端服务器地址

资料来源

  1. Kubesphere 源码分析1 整体结构

  2. 容器化部署方案_半只青年的博客-CSDN博客

  3. kubesphere console 二次开发源码阅读_kube-design_tina_sprunt的博客-CSDN博客

  4. blog.csdn.net

  5. https://cloud.tencent.com/developer/beta/article/1865745

  6. Documentation

  7. 词汇表

  8. KubeSphere简介,功能介绍,优势,架构说明及应用场景_kybesphere_爱是与世界平行的博客-CSDN博客

  9. 一文看懂 Webhook 是什么?怎么使用?

  10. 什么是 Webhook?

  11. kubesphere console 二次开发源码阅读_kube-design_tina_sprunt的博客-CSDN博客

  12. React 18中MobX的使用 - 掘金

  13. Redux 核心概念

  14. Redux的三大核心 - 掘金

  15. mobx、mobx-react和mobx-react-lite新手入门 - 掘金

  16. 初探mobx-react-lite

  17. https://mp.weixin.qq.com/s/AsH0nRYr3hDF2Zr4_KQOCA

  18. mobx 的原理以及在 React 中应用 - 掘金

  19. mobx在react如何使用?3分钟学会!

  20. Mobx-React : 当前最适合React的状态管理工具| 青训营笔记 - 掘金

  21. blog.csdn.net

  22. React 核心总结 - 掘金

  23. react调度源码-切片原理 - 掘金

  24. 【React 18.2 源码学习】React render 原来你是这样的 - 掘金

  25. 走进React Fiber的世界 - 掘金

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/272079.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS-系统弹窗调用

代码&#xff1a; UIAlertController *alertViewController [UIAlertController alertControllerWithTitle:"请选择方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];// style 为 sheet UIAlertAction *cancle [UIAlertAction actionWithTit…

纳斯达克大屏:NASDAQ广告大屏多少钱?

大舍传媒 近期&#xff0c;美国纽约纳斯达克大屏广告&#xff08;NASDAQ广告大屏&#xff09;备受瞩目&#xff0c;众多企业纷纷关注其广告投放效果以及费用。纳斯达克大屏广告的价格究竟是多少呢&#xff1f;下面我们从事件的经过、相关背景信息以及对其影响和意义的分析等方…

革命性创新!AI大模型开发崭新风貌

在当今科技日新月异的时代&#xff0c;人工智能&#xff08;AI&#xff09;作为一项颠覆性的技术&#xff0c;正以革命性的创新改变着我们的生活方式和工作方式。而在AI领域中&#xff0c;大模型的开发更是成为了展示技术实力和提升智能化水平的重要标志。 随着数据量的不断增…

STM32 NAND FLASH知识点

1.NAND FLASH的简介 NAND FLASH 的概念是由东芝公司在 1989 年率先提出&#xff0c;它内部采用非线性宏单元模式&#xff0c;为固态大容量内存的实现提供了廉价有效的解决方案。 NAND FLASH 存储器具有容量较大&#xff0c;改写速度快等优点&#xff0c;适用于大量数据的存储&…

windows关闭copilot预览版

如果用户不想在windows系统当中启用Copilot&#xff0c;可以通过以下三种方式禁用。 第一种&#xff1a;隐藏Copilot 按钮 右键点击任务栏&#xff0c;取消勾选“显示 Copilot&#xff08;预览版&#xff09;按钮”&#xff0c;任务栏则不再显示&#xff0c;用户可以通过快捷键…

外包干了5天,技术退步明显。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…

Java后端核心——Servlet

目录 一.概述 二.基础实现 1.导入坐标 2.定义实现类 3.注解 4.访问Servlet 三.执行流程 四.生命周期 1.加载和实例化 2.初始化 3.请求处理 4.服务终止 五.方法 1.init 2.service 3.destroy 4.getServletInfo 5.getServletConfig 六.体系结构 七.urlPatter…

Kafka MQ 主题和分区

Kafka MQ 主题和分区 Kafka 的消息通过 主题 进行分类。主题就好比数据库的表&#xff0c;或者文件系统里的文件夹。主题可以被分为若干个 分区 &#xff0c;一个分区就是一个提交日志。消息以追加的方式写入分区&#xff0c;然 后以先入先出的顺序读取。要注意&#xff0c;由…

uniapp 手写 简易 时间轴 组件

一、案例如图 该案例设计条件&#xff1a; 左侧时间 和竖线、点、内容都是居中对其的&#xff0c;上下时间点中间要有一段距离 二、编写逻辑 1. 布局结构&#xff1a;一共三个元素&#xff0c;左侧是时间和黑点&#xff0c;中间是线条&#xff0c;右侧是内容 2. 样式难点&#…

stm32普通定时器脉冲计数(发送固定脉冲个数),控制步进电机驱动器

拨码开关设置驱动器&#xff0c;细分 方法思路&#xff1a;用通用定时器TIM2&#xff0c;1ms产生一次中断&#xff1b;在中断里做IO反转&#xff1b; 发送10个脉冲信号

iOS——【自动引用计数】ARC规则及实现

1.3.3所有权修饰符 所有权修饰符一共有四种&#xff1a; __strong 修饰符__weak 修饰符__undafe_unretained 修饰符__autoreleasing 修饰符 __strong修饰符 _strong修饰符表示对对象的强引用&#xff0c;持有强引用的变量在超出其作用域的时候会被废弃&#xff0c;随着强引…

数据结构之栈详解(C语言手撕)

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

二维码门楼牌管理系统技术服务:构建智慧城市新标准

文章目录 前言一、二维码门楼牌管理系统的诞生背景二、标准地址编码的定义与作用三、二维码门楼牌管理系统的核心技术四、二维码门楼牌管理系统的应用优势五、二维码门楼牌管理系统在智慧城市建设中的作用六、结论与展望 前言 随着城市化的快速发展&#xff0c;传统的门楼牌管…

Claude 3 Sonnet 模型现已在亚马逊云科技的 Amazon Bedrock 正式可用!

今天&#xff0c;我们宣布一个激动人心的里程碑&#xff1a;Anthropic 的 Claude 3 Sonnet 模型现已在亚马逊云科技的 Amazon Bedrock 正式可用。 下一代 Claude (Claude 3) 的三个模型 Claude 3 Opus、Claude 3 Sonnet 和 Claude 3 Haiku 将陆续登陆 Amazon Bedrock。Amazon …

StarRocks实战——欢聚集团极速的数据分析能力

目录 一、大数据平台架构 二、OLAP选型及改进 三、StarRocks 经验沉淀 3.1 资源隔离&#xff0c;助力业务推广 3.1.1 面临的挑战 3.1.2 整体效果 3.2 稳定优先&#xff0c;监控先行&#xff0c;优化运维 3.3降低门槛&#xff0c;不折腾用户 3.3.1 与现有的平台做打通 …

安卓7原生相机切到视频崩溃

目录 1、查看日志 2、分析日志、提取重点 3、寻找解决方法 author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye_嵌入式,Linux,Qt-CSDN博客daisy.skye擅长嵌入式,Linux,Qt,等方面的知识https://blog.csdn.net/qq_40715266?typeblog 1、查看日志 由于安…

基于YOLOv8深度学习的智能道路裂缝检测与分析系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、目标分割

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

vue 在线预览word

1 mammoth 先找的是mammoth这个插件yarn add mammoth,版本是1,7.0 参考网上的示例使用如下&#xff1a; import mammoth from "mammoth"; const vHtml ref("") const readExcelFromRemoteFile (url) >{var xhr new XMLHttpRequest();xhr.open("…

2024 RubyMine 激活,分享几个RubyMine 激活的方案

文章目录 RubyMine 公司简介我这边使用RubyMine 的理由RubyMine 2023.3 最新变化AI Assistant 正式版对 AI 生成名称建议的支持改进了 Ruby 上下文单元测试生成 RailsRails 应用程序和引擎的自定义路径Rails 路径的自动导入对存储在默认位置之外的模型、控制器和邮件器的代码洞…

gitte上传项目操作

一、项目背景 打比赛&#xff0c;多个人合作&#xff0c;选择github&#xff0c;顺便了解下git的代码操作。 二、步骤 2.1 新建仓库 2.2 打开你要上传到库的项目 2.2 选择 Git Bash Here 输入指令 git init 2.3 查找github的仓库 2.2 将文件放入暂缓区 git add . 2.3填写…