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}`}>
  <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的世界 - 掘金

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手写分布式配置中心(六)整合springboot(自动刷新)

对于springboot配置自动刷新&#xff0c;原理也很简单&#xff0c;就是在启动过程中用一个BeanPostProcessor去收集需要自动刷新的字段&#xff0c;然后在springboot启动后开启轮询任务即可。 不过需要对之前的代码再次做修改&#xff0c;因为springboot的配置注入value("…

pytest教程-15-多个fixture以及重命名

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了fixture的yield关键字&#xff0c;本小节我们讲解一下使用多个fixture的方法。 使用多个fixture 如果用例需要用到多个fixture的返回数据&#xff0c;fixture也可以return一个元组、list或字…

[嵌入式系统-37]:龙芯1B 开发学习套件 -7-MIPS指令集

目录 一、MIPS指令分类 二、常用指令详解 三、常用MIPS指令集及格式&#xff1a; 四、CPU内部的32个寄存器&#xff1a; 一、MIPS指令分类 MIPS&#xff08;Microprocessor without Interlocked Pipeline Stages&#xff09;指令集是一种广泛用于教学和嵌入式系统的指令集…

在线部署ubuntu20.04服务器,安装jdk、mysql、redis、nginx、minio、开机自启微服务jar包

一、服务器 1、查看服务器版本 查看服务器版本为20.04 lsb_release -a2、服务器信息 服务器初始账号密码 sxd / 123456 首先,更改自身密码都输入123456 sudo passwd 创建最高权限root账号&#xff0c;密码为 123456 su root 3、更新服务器源 1、更新源列表 sudo apt-g…

【golang】Windows与Linux交叉编译保姆级教程

【golang】Windows与Linux交叉编译 大家好 我是寸铁&#x1f44a; 总结了一篇【golang】Windows与Linux交叉编译的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题背景 今天寸铁想将Windows中的程序部到Linux下跑&#xff0c;我们知道在从Windows与Linux下要进行交叉编译…

11、Linux-安装和配置Redis

目录 第一步&#xff0c;传输文件和解压 第二步&#xff0c;安装gcc编译器 第三步&#xff0c;编译Redis 第四步&#xff0c;安装Redis服务 第五步&#xff0c;配置Redis ①开启后台启动 ②关闭保护模式&#xff08;关闭之后才可以远程连接Redis&#xff09; ③设置远程…

Java基础 - 8 - 算法、正则表达式、异常

一. 算法 什么是算法&#xff1f; 解决某个实际问题的过程和方法 学习算法的技巧&#xff1f; 先搞清楚算法的流程&#xff0c;再直接去推敲如何写算法 1.1 排序算法 1.1.1 冒泡排序 每次从数组中找出最大值放在数组的后面去 public class demo {public static void main(S…

21 easy 1. 两数之和

//给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 // // 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 // // 你可以…

java Spring boot简述jetcache 并叙述后续文章安排

我们之前 讲了 Spring boot 整合 cache 使用 simple(默认) redis Ehcache memcached的几种方式 但是 始终有人觉得不够完善 提出了一些问题 例如 觉得 当前spring boot 对缓存过期的控制过于松散 不严谨 比较明显的体现就是 memcached过期时间在逻辑代码中控制 Ehcache的过期时…

Tomcat的安装

下载Tomcat&#xff08;这里以Tomcat8.5为例&#xff09; 直接进入官网进行下载&#xff0c;Tomcat官网 选择需要下载的版本&#xff0c;点击下载这里一定要注意&#xff1a;下载路径一定要记住&#xff0c;并且路径中尽量不要有中文&#xff01;&#xff01;&#xff01;&…

Prompt Engineering、Finetune、RAG:OpenAI LLM 应用最佳实践

一、背景 本文介绍了 2023 年 11 月 OpenAI DevDay 中的一个演讲&#xff0c;演讲者为 John Allard 和 Colin Jarvis。演讲中&#xff0c;作者对 LLM 应用落地过程中遇到的问题和相关改进方案进行了总结。虽然其中用到的都是已知的技术&#xff0c;但是进行了很好的总结和串联…

神经网络的矢量化,训练与激活函数

我们现在再回到我们的神经元部分&#xff0c;来看我们如何用python进行正向传递。 单层的正向传递&#xff1a; 我们回到我们的线性回归的函数。我们每个神经元通过上述的方法&#xff0c;就可以得到我们的激发值&#xff0c;从而可以继续进行下一层。 我们用这个方法就可以得…

排序算法的对比

类别排序方法时间复杂度空间复杂度稳定性平均情况特殊情况 插入 排序 插入排序基本有序最优稳定希尔排序不稳定 选择 排序 选择排序不稳定堆排序不稳定 交换 排序 冒泡排序稳定快速排序基本有序最差不稳定归并排序稳定基数排序稳定

0环PEB断链实现

截止到昨天那里我们的思路就清晰了&#xff0c;通过EPROCESS找到我们要隐藏的进程的ActiveProcessLinks&#xff0c;将双向链表的值修改&#xff0c;就可以将我们想要隐藏的这个进程的ActiveProcessLinks从双向链表中抹去的效果&#xff0c;这里的话如果在windbg里面直接使用ed…

用Python实现一个简单的——人脸相似度对比

近几年来&#xff0c;兴起了一股人工智能热潮&#xff0c;让人们见到了AI的能力和强大&#xff0c;比如图像识别&#xff0c;语音识别&#xff0c;机器翻译&#xff0c;无人驾驶等等。总体来说&#xff0c;AI的门槛还是比较高&#xff0c;不仅要学会使用框架实现&#xff0c;更…

Day33|贪心算法part3

k次取反后最大的数组元素和 思路&#xff1a;贪心&#xff0c;局部最优&#xff0c;让绝对值大的负数变正数&#xff0c;当前数值达到最大&#xff0c;整体最优&#xff1b;整个数组和达到最大。如果把序列中所有负数都转换为正数了&#xff0c;k还没耗尽&#xff0c;就是k还大…

AWS的CISO:GenAI只是一个工具,不是万能钥匙

根据CrowdStrike的年度全球威胁报告,尽管研究人员预计人工智能将放大对防御者和攻击者的影响,但威胁参与者在其行动中使用人工智能的程度有限。该公司上个月在报告中表示:“在整个2023年,很少观察到GenAI支持恶意计算机网络运营的开发和/或执行。” 对于GenAI在网络安全中的…

Python:在 Ubuntu 上安装 pip的方法

目录 1、检测是否已安装pip 2、更新软件源 3、安装 4、检测是否安装成功 pip和pip3都是Python包管理工具&#xff0c;用于安装和管理Python包。 在Ubuntu上&#xff0c;pip和pip3是分别针对Python2和Python3版本的pip工具。 pip3作用&#xff1a;自动下载安装Python的库文…

2024年HW技术总结

我们都知道&#xff0c; 护网行动 是国家应对网络安全问题所做的重要布局之一。至今已经是8个年头了&#xff0c;很多公司在这时候人手不够&#xff0c;因此不得不招募一些网安人员来参加护网。 红队 扮演攻击的角色&#xff0c;蓝队 扮演防守、溯源的角色&#xff0c;紫队当然…

Java 的 System 类常用方法介绍

Java 中的 System 类是一个final类&#xff0c;它提供了与系统相关的属性和方法。它是一个内置的类&#xff0c;可以直接使用&#xff0c;不需要实例化。System 类提供了标准输入、标准输出和错误输出流&#xff0c;以及对外部定义的属性和系统环境的访问。下面是 System 类的一…