Kubesphere前端项目分析

news2024/11/16 7:26:19

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}`}>
  <Icon
    name={COMPONENT_ICON_MAP[item.type]}
    size={44}
    clickable
  />
</Link>
<NavLink
              key={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}`}>
  <Icon
    name={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.props

    return (
      <Columns className={classNames('is-variable is-2', className)}>
        <Column className="is-narrow">
          <img
            className={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 (
    <ReactFileReader
      fileTypes={['.yaml', '.txt']}
      handleFiles={handleUpload}
    >
      <Icon
        name="upload"
        size={20}
        clickable
        changeable
      />
    </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, // 标记observable
      price: 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 {
  @observable
  navs = globals.config.navs

  @observable
  showGlobalNav = false

  @observable
  actions = {}

  @observable
  oauthServers = []

  constructor() {
    this.websocket = new WebSocketStore()

    this.user = new UserStore()
    this.routing = new RouterStore()
    this.routing.query = this.query

    global.navigateTo = this.routing.push
  }

  register(name, store) {
    extendObservable(this, { [name]: store })
  }

  query = (params = {}, refresh = false) => {
    const { pathname, search } = this.routing.location
    const currentParams = parse(search.slice(1))

    const newParams = refresh ? params : { ...currentParams, ...params }

    this.routing.push(`${pathname}?${getQueryString(newParams)}`)
  }

  @action
  toggleGlobalNav = () => {
    this.showGlobalNav = !this.showGlobalNav
  }

  @action
  hideGlobalNav = () => {
    this.showGlobalNav = false
  }

  @action
  registerActions = actions => {
    extendObservable(this.actions, actions)
  }

  @action
  triggerAction(id, ...rest) {
    this.actions[id] && this.actions[id].on(...rest)
  }

  login(params) {
    return request.post('login', params)
  }

  @action
  async logout() {
    const res = await request.post('logout')
    const url = get(res, 'data.url')
    if (url) {
      window.location.href = url
    }
  }

  @action
  getRules(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.props

    return 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.props

    return (
      <Form.Group
        label={t('ENVIRONMENT_VARIABLE_PL')}
        desc={t('CONTAINER_ENVIRONMENT_DESC')}
        checkable={checkable}
      >
        <Form.Item rules={[{ validator: this.envValidator }]}>
          <EnvironmentInput
            rootStore={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.coloradmin.cn/o/1502324.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

javascript:void(0);用法及常见问题解析

在Web开发中&#xff0c;javascript:void(0);是一个经常被用到的代码片段&#xff0c;特别是在一些老式的网页中。这个代码片段的作用是执行一个空操作&#xff08;null operation&#xff09;&#xff0c;即不执行任何操作。它的主要用途是在JavaScript代码中创建一个空链接&a…

JavaScript数组常见实例方法:forEach、filter、map、reduce、find、every等

博客背后的故事 其实我23年7月就学过这些数组方法了&#xff0c;但是为什么24年3月才做笔记呢&#xff1f;这就要讲一个小故事了&#xff08;不想听故事的同学自行拖动滚动条&#xff09; 24年年初我和两个队友合作开发一个小程序。JavaScript中数组的实例方法我已经学了很久…

阿波罗登月需要解决飞行控制问题,数学家卡尔曼在维纳控制的基础上提出了卡尔曼滤波

说到登月&#xff0c;很多人只想到和火箭以及航天器相关的技术&#xff0c;其实登月离不开信息技术的革命。因为从飞行控制到远程通信&#xff0c;都需要解决很多过去从未遇到过的难题。 登月首先要保证在月球上着陆的地点准确&#xff0c;而且要保证返回火箭和飞船能够在月球轨…

sizeof和strlen的详细万字解读

sizeof和strlen的对比 sizeof不是函数 侧面证明sizeof不是函数 如果是函数 应该需要有括号 不能落下来 strlen 只针对字符串 包含头文件 string.h 并且这个是个函数 随机数值 sizeof里面有表达式的话 表达式里面是不参与计算的 下面的s求出的是4 就是因为是不参与计算的 不…

opencv dnn模块 示例(24) 目标检测 object_detection 之 yolov8-pose 和 yolov8-obb

前面博文【opencv dnn模块 示例(23) 目标检测 object_detection 之 yolov8】 已经已经详细介绍了yolov8网络和测试。本文继续说明使用yolov8 进行 人体姿态估计 pose 和 旋转目标检测 OBB 。 文章目录 1、Yolov8-pose 简单使用2、Yolov8-OBB2.1、python 命令行测试2.2、opencv…

图机器学习(3)-面向节点的人工特征工程

0 问题引入 地铁导航图 计算机是看不懂这些图&#xff0c;计算机只能看懂向量、矩阵。 传统图机器学习只讨论连接特征。 构造一个新的特征 x 1 x 2 x_1x_2 x1​x2​&#xff0c;有利于分开这种数据。 人需要去翻译这些计算机不懂的特征&#xff0c;变成计算机可以懂…

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

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

「2024指南」tf卡格式化了数据怎么恢复?

咨询&#xff1a;我把TF卡插入了我的安卓手机并将其设为内部存储&#xff0c;然后保存了大量重要资料。不久后&#xff0c;我无意中将TF卡拔出。当我再次插入时&#xff0c;手机提示必须格式化TF卡。我不小心点击了格式化选项&#xff0c;导致里面所有重要的资料都不见了。请问…

Java基础语法深入讲解

Java是一种面向对象的编程语言&#xff0c;由Sun Microsystems公司于1995年发布。它被设计为具有跨平台兼容性&#xff0c;这得益于Java虚拟机&#xff08;JVM&#xff09;的架构&#xff0c;允许开发者“一次编写&#xff0c;到处运行”&#xff08;Write Once, Run Anywhere&…

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

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

Maven基础简介

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构&#xff0c;spring等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; Maven简介 Maven是什么 Maven…

day12_SpringCloud(Gateway,Nacos配置中心,Sentinel组件)

文章目录 1 Gateway组件1.1 Gateway简介1.2 Gateway入门1.3 网关路由流程图1.4 路由工厂1.5 过滤器1.5.1 过滤器简介1.5.2 内置过滤器1.5.3 路由过滤器1.5.4 默认过滤器1.5.5 全局过滤器1.5.6 过滤器执行顺序 2 Nacos配置中心2.1 统一配置管理2.2 Nacos入门2.2.1 Nacos中添加配…

有趣的数学 博弈论初探

1、简述 博弈论是数学的一个分支&#xff0c;专注于分析此类博弈。博弈论可以分为两个主要分支学科&#xff1a;经典博弈论和组合博弈论。经典博弈论研究玩家同时移动、下注或制定策略的游戏。 从数学意义上讲&#xff0c;游戏是指玩家根据定义的规则做出理性决策&#xff0c;试…

js小案例-省市级联

运行效果&#xff1a; 代码演示&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>省市级联</title><style type"text/css">.regist_bg {width: 100%;height: 600px;padding-top: 40px…

四元数(Quaternion)的一些性质

四元数(Quaternion)是用于三维旋转和定向的四部分组成的超复数&#xff0c;超复数简单理解就是比abi这样的复数更复杂的复数&#xff0c;其中abi这样的复数我们也可以叫做二元数&#xff0c;表示复平面的一点&#xff0c;对于熟悉欧拉公式的朋友就知道&#xff0c;也可以看成是…

请编程输出无向无权图各个顶点的度 ← 邻接矩阵存图

【题目描述】 请编程输出无向无权图各个顶点的度。【测试样例示意图】【算法代码】 #include <bits/stdc.h> using namespace std; const int maxn100; int mp[maxn][maxn]; //无向无权图的邻接矩阵 int V,E; //顶点数、边数 int sx,ex; //起点编号、终点编号int main()…

通过Electron打包前端项目为exe

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;爱蹦跶的大A阿 &#x1f525;当前正在更新专栏&#xff1a;《JavaScript保姆级教程》、《VUE》、《Krpano》 ✨ 正文 1、 拉取electron官网上的demo&#xff0c;拉下来之后安装依赖&#xff0c;项目跑起来之后&#xff0c;就…

Python并发编程:非阻塞IO

非阻塞IO&#xff08;non-blocking IO&#xff09; Linux下&#xff0c;可以通过设置socket使其变为non-blocking&#xff0c;当对一个non-blocking socket执行读操作时&#xff0c;流程是这个样子 从图中可以看出&#xff0c;当用户进程发出read操作时&#xff0c;如果kernel…

2024超声波清洗机测评推荐、希亦、苏泊尔、德国综合对比哪个牌子好

清洁眼镜的重要性不言而喻&#xff0c;干净的眼镜不仅可以提供清晰的视野&#xff0c;还可以保护眼睛免受有害物质的侵害。并且现在有非常多的盆骨都是离不开眼镜的&#xff0c;像近视的朋友需要经常佩戴眼镜来配合自己看远处实现&#xff0c;经常爬山的朋友需要墨镜来协助我们…

基于SpringBoot+Vue+ElementUI+Mybatis前后端分离管理系统超详细教程(五)——多条件搜索并分页展示

前后端数据交互 书接上文&#xff0c;我们上节课通过前后端数据交互实现了分页查询和单条件搜索分页查询的功能&#xff0c;最后留了个小尾巴&#xff0c;就是把其他两个搜索条件&#xff08;email,address&#xff09;也加进来&#xff0c;实现多条件搜索并分页展示。这节课我…