Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
T treasure
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 12
    • Issues 12
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • External wiki
    • External wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • FE
  • treasure
  • Issues
  • #132

Closed
Open
Created Feb 26, 2023 by liuyajun@liuyajunMaintainer

React 和 Vue 差异,React 开发总结

React 和 Vue 差异,React 业务项目开发总结

文档地址

  • Vue https://vuejs.bootcss.com/guide/
  • React https://react.docschina.org/docs/hello-world.html

学习方式

  • 边做边学,请从实践教程开始。
  • 一步步学习概念,请从 Hello World 开始。
  • ...

编写特点

  • vue
    • 传参以 数据 为主,事件传递较少,组件以插槽形式传入
    • HTML 的模板语法(也支持 JSX)
  • react
    • 万物皆可 props,数据、事件、组件都可以作为 props 传递
    • JSX,也可以不使用 JSX 的 React

差异

生命周期-初始渲染

Vue

  • created
  • mounted

React

image 暂时只需要关注 useEffect,见下面 钩子函数 部分

Hook 钩子函数

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

react 中常见的 hook

常见的Hook

  • useState 修改 state
  • useEffect 操作副作用,对 DOM 的更改后运行你的“副作用”函数
  • useMemo 返回一个缓存的值
    • 类似 vue 的 computed
    • 计算结果缓存
    • 优化有助于避免在每次渲染时都进行高开销的计算
    • 在渲染期间执行,即在 DOM 更新前触发的
    • 类比生命周期就是 shouldComponentUpdate
  • useRef 通过 ref 访问 DOM,useRef 会在每次渲染时返回同一个 ref 对象
  • useContext 实现跨组件间的数据传输
  • useCallback 返回的是缓存的函数
    • const fnA = useCallback(fnB, [a])
    • 当属性 a 发生变动时,返回新的函数 fnB
    • 和 useMemo 类似,但是返回的是缓存的函数
  • useReducer 小范围内的状态管理工具
    • const [state, dispatch] = useReducer(reducer, initialState)
    • 作为 useState 的替代方案
    • 【React全解6】useReducer的使用详解和代替Redux
    • React 中的useReducer是个什么东西
    • 【React】useContext与useReducer结合实现状态管理

useEffect

useEffect(Function, Array)

react里useEffect、useMemo的区别

  • useEffect 和生命周期有关,类似 mounted + watch
  • useMemo 类似 computed

useEffect 的第二个参数,有三种情况

  1. 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
  2. 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
  3. 传入一个数组,其中包括变量,只有这些变量任意一个变动时,useEffect 才会执行;有点像 useWacth
const [count, setCount] = useState(0);
 
useEffect(() => {
    console.log('xxx') // 表示你要执行的动作
}, [count]); // 表示在count的值发生变化的时候,useEffect中的方法再执行一次,类似componentDidUpdate函数

useEffect(() => {
    const timer = setInterval(() => {
      // do something
    }, 1000);
 
    // 表示在组件销毁的时候执行清楚定时器的动作,类似于componentWillUnmount函数
    return () => clearInterval(timer);
  }, []);

useMemo(主要针对当前组件中使用函数,优化性能)

useEffect和组件的生命周期有关,但 useMemo 跟生命周期不挂钩。

useMemo 是性能优化的手段,类似于类组件中的 shouldComponentUpdate,在子组件中使用 shouldComponentUpdate, 判定该组件的 props 和 state 是否有变化,从而避免每次父组件render时都去重新渲染子组件。

const [ count, setCount ] = useState(0)
const add = useMemo(() => count + 1 , [count])
 

<div>
  点击次数: { count }<br/>
  次数加一: { add }<br/>
  <button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
实例
  // 表单数据回显
  useEffect(() => {
    console.log(1);
    form.setFieldsValue({
      ...data
    });
  }, [data]);
  
 useEffect(() => {
    form.setFieldValue('roles', Array.isArray(dataSource) ? dataSource.map(item => ({
      multiPerson: item.multiPerson,
      name: item.name,
      id: item.id,
      employees: [],
    })) : []);
  }, [dataSource]);

数据流

Vue

  • 双向绑定
  • 单向数据流
this.name = '张三'

<input v-model="value" />

React

  • 单向数据流
  • 万物皆 Props
  • 主要通过 onChange/setState()
  • 不支持修改 props 数据,会报错
const { name, setName } = useState('');
setName('张三');

// 会报错,props的值不可修改
<input value={this.props.value}/>

// 在onChange调用setState修改数据,需要调用setState修改绑定数据
<input value={this.state.value} onChange={this.onChange}/>

传参

React

事件/函数
<AppCard key={index} title={item.title} onClick={item.onClick} />;

<Button onClick={cancel}>取消</Button>
变量

<Button type='primary' disabled={requesting} onClick={confirm}>确定</Button>

样式
<Title style={{ margin: 0, paddingLeft: 32 }} level={4}>新增采购单</Title>
<Form form={form} className={styles.form}>
组件

<Layout bodyStyle={{ height: 'calc(100vh - 160px)' }} actions={Operate()}>

实例
<div className={styles.container}>
  <PageContainer noMargin>
    <Layout actions={Operate()} bodyStyle={{ height: 'calc(100vh - 215px)' }}>
      <div className={styles.formContainer}>
        <Form {...layout} labelAlign='left' form={form} colon={false} className={styles.form}>
          <div className='fn-20 lh-28 font-weight-500 mb-16'>新增采购单</div>
          <Basic form={form} updateExtraFormData={updateExtraFormData} />
          <Places form={form} />
          <Role />
        </Form>
      </div>
    </Layout>
  </PageContainer>
</div>

判断隐藏/显示

Vue v-if

<Process v-if="curStep === 1"/>

React

  • 短路运算符 {curStep === 1 && <Process/>}
  • 三目运算符 {curStep === 1 ? <Process/> : null}

循环

Vue v-for

<AppCard v-for="(item, index) in list" :key="index" :title="item.title" @click="item.onClick" />

React map

{
  list.map((item, index) => {
    return <AppCard key={index} title={item.title} onClick={item.onClick} />;
  });
}

样式

Vue 不做赘述

React

单个 className
import styles from './entry.module.less';
<Form form={form} className={styles.form}>

entry.module.less

.form {
  width: 80%;
  margin: 0 auto;

  :global {

    .resant-form-item {

      &-label {
        width: 80px;
  
        // > label {
        //   color: #768098;
        // }
      }

      &.role .resant-form-item-label {
        white-space: normal;
        word-break: break-all;
        overflow: initial;
      }

    }

  }
}

:global 样式局部作用域,类似 /deep/

react的CSS中 :global的含义

多样式写法
import cs from 'classnames';

<div className={cs(styles['extra-amount-item'], styles['extra-amount-total'], !Array.isArray(extra_amounts) || !extra_amounts.length ? styles['no-amount'] : '')}>
style
<Title style={{ margin: 0, paddingLeft: 32 }} level={4}>新增采购单</Title>
<Form form={form} className={styles.form}>

样式普通写法

import './Container.less';

return <div className='detail-container' style={style}>
  {/* 内容主体 */}
  <div className='detail-container__main'>{children}</div>
</div>;

src/common/components/Detail/Container.less

.detail-container {
  display: flex;
  flex-direction: column;
  height: 100%;

  &__main {
    overflow-y: auto;
    flex-grow: 1;
  }
}

样式 module 写法

import styles from './Container.module.less';


return <div className={styles['detail-container']} style={style}>
  {/* 内容主体 */}
  <div className='detail-container__main'>{children}</div>
</div>;

src/common/components/Detail/Container.module.less 只有加上 :global 后,.detail-container__main 才生效

.detail-container {
  display: flex;
  flex-direction: column;
  height: 100%;

 :global { 

  .detail-container__main {
    overflow-y: auto;
    flex-grow: 1;
  }
 }
}

组件嵌套

Vue

  • 默认插槽 <slot></slot>
  • 具名插槽 <slot name="label"></slot>

React

  • 默认插槽,类似 Vue 的default 默认插槽,通过组件的 children 参数使用该插槽
  • 具名插槽,通过组件的其他 props 属性入参

跳转

Vue

this.$router.push('/purchase');

React

项目中的跳转

import { dispatchNavigate } from '@/common/document-event/dispatch';

dispatchNavigate('/purchase');

组件通信

Vue

  • props/emit
  • provide/inject
  • ref
  • vuex(双向数据绑定,响应式)
  • event bus
  • ref

React

  • props(子传父通过 props.function(...))
  • ref
  • context
  • redux(单向数据流)

表单

Vue

ElementUI

el-form

React

Ant Design

内部封装了 value、change,https://ant.design/components/form-cn#formitem

被设置了 name 属性的 Form.Item 包装的控件,表单控件会自动添加 value(或 valuePropName 指定的其他属性) onChange(或 trigger 指定的其他属性),数据同步将被 Form 接管

示例

<Form form={form}>
  <div className='fn-16 lh-22 font-weight-500 mb-20'>{fieldName}</div>

  <div className='fn-14 lh-20 font-weight-500 mb-16'>1.点位信息</div>
  <div className='flex-row'>
    <FormRangePicker label='活动日期' name='date' rules={[{ required: true, message: '请选择活动日期' }]} formItemConfig={{ className: 'mr-20' }} />
    <FormDatePicker label='进场时间' name='enter_time' placeholder='请选择进场时间' />
  </div>
  <div className='flex-row'>
    <FormDatePicker label='撤场时间' name='exit_time' formItemConfig={{ className: 'mr-20' }} placeholder='请选择撤场时间' />
    <Form.Item label='场地面积' name='size'><div className={styles.size}>{size}</div></Form.Item>
  </div>
</Form>

常用表单事件

  • form.validateFields(); 校验

  • form.getFieldValue('user_id'); 获取表单字段

  • form.getFieldsValue(); 获取表单所有字段

  • Form.useWatch('user_id', form); 监听表单字段

  • form.setFieldValue('user_id', 1); 设置表单字段

  • form.setFieldsValue({ 'user_id': 1 }); 批量设置表单字段

获取表单字段
const [formData, setFormData] = useState<any>({});

const onChange = (values, a) => {
  console.log('onChange values', values, a);
  setFormData(values);
};

const user_id = Form.useWatch('user_id', form);

<div>form.getFieldValue:{form.getFieldValue('user_id')}</div>
<div>form.getFieldsValue:{form.getFieldsValue().user_id}</div>
<div>formData.user_id:{formData.user_id}</div>
<div>user_id:{user_id}</div>

表单校验规则

antd 的校验方式基本和 element 相似

Vue

组件上使用
<FormInput
  v-model="row.remark"
  :prop="`plans.${index}.remark`"
  type="textarea"
  maxlength="200"
  :form-item-config="{ 'label-width': '0' }"
  placeholder="请填写备注,最多可输入200字"
/>
validator

区别:

  • 成功 callback();
  • 失败 return callback(Error('请输入两位小数点小数'));
'purchased_money': [{
  validator: (rule, value, callback) => {
    if ((purchased_money_from && !TWO_DECIMAL_NUMBER_REG.test(purchased_money_from)) ||
      (purchased_money_to && !TWO_DECIMAL_NUMBER_REG.test(purchased_money_to))) {
      return callback(Error('请输入两位小数点小数'));
    }
    if (String(purchased_money_from) && String(purchased_money_to) && +purchased_money_to < +purchased_money_from) {
      return callback(Error('最小值不能大于最大值'));
    }
    callback();
  },
}],

React

组件上使用
<FormInputPassword
  label='新密码'
  name='newPassword'
  rules={[{ required: true, message: '请输入新密码' }, { pattern: EIGHT_SIXTEEN_PASSWORD_REG, message: '密码格式错误' }]}
/>
validator

区别:

  • 成功 return Promise.resolve();
  • 失败 return Promise.reject(new Error('请输入两位小数点小数'));
// 金额付款计划校验
const totalAmount = 100;
const amountPlansRules = [{
  validator: (rule, value) => {
    if (value.some(item => isNotEmpty(item.amount) && !TWO_DECIMAL_NUMBER_REG.test(item.amount))) {
      return Promise.reject(new Error('请输入两位小数点小数'));
    }
    return Promise.resolve();
  },
}, {
  validator: (rule, value) => {
    const result = Array.isArray(value) ? value.reduce((result, item) => {
      return floorKeep(result, item.amount, 2);
    }, 0) : 0;
    if (result !== +totalAmount) {
      return Promise.reject(new Error(`总金额 ${totalAmount} 元与分期总金额不一致,请检查`));
    }
    return Promise.resolve();
  },
}];

获取表单多层级的字段

下面以动态表单获取 amount_plans 数组内的 name 字段为例

formData = {
  amount_plans: [
    { name: null, amount: 200 },
    { name: null, amount: 300 },
  ]
}

Vue

:prop="`amount_plans.${index}.name`"

<FormInput v-model="row.name" :prop="`plans.${index}.name`"/>

React

name={['amount_plans', index, 'name']}

<FormInput name={['amount_plans', index, 'name']}/>

动态表单

React

使用 Form.List 组件
form.setFieldsValue({
  amount_plans: [
    { name: null, amount: 200 },
    { name: null, amount: 300 },
  ]
});


// 用于实时打印 amount_plans
const amount_plans = Form.useWatch('amount_plans', form);

{JSON.stringify(amount_plans, null, 2)}

{/* 绑定 amount_plans[0].amount */}
<FormInputNumber
  name={['amount_plans', 0, 'amount']}
  min={0}
  max={9999999999.99}
  config={{ addonAfter: '元' }}
  placeholder='请输入收款金额'
/>

<Form.List name='amount_plans'>
  {(fields) => (
    <>
      {Array.isArray(fields) && fields.map((item: any, index) => (
        <div key={index} className={cs(styles.flex, styles.plans)}>
          {/* { "name": 0, "key": 0, "isListField": true, "fieldKey": 0 } */}
          {JSON.stringify(item, null, 2)}
          <FormDatePicker
            name={[item.name, 'time']}
            rules={[{ required: true, message: '请选择付款日期' }]}
            formItemConfig={{ className: cs('mr-20', 'mb-8', styles['plans__date']) }}
            placeholder='请选择收款日期'
          />
          <FormInputNumber
            name={[item.name, 'amount']}
            min={0}
            max={9999999999.99}
            config={{ addonAfter: '元' }}
            rules={[{ pattern: TWO_DECIMAL_NUMBER_REG, message: '请输入两位小数点小数', }]}
            formItemConfig={{ className: cs('mr-20', 'mb-8', styles['plans__amount']) }}
            placeholder='请输入收款金额'
          />
          {/* 等同于 name={[item.name, 'amount']} 的写法 */}
          <FormInputNumber
            name={[index, 'amount']}
            min={0}
            max={9999999999.99}
            config={{ addonAfter: '元' }}
            placeholder='请输入收款金额'
          />
        </div>
      ))}
    </>
  )}
</Form.List>
使用 Group 组件
<Form.Item name='amount_plans' label='付款计划' className={styles.plans} rules={amountPlansRules} >
  <Group value={amount_plans} setValue={setPlans} getOriData={getOriPlan} dittoCoverData={{ amount: null }}>
    {(item, index) => <>
      <FormDatePicker
        name={['amount_plans', index, 'time']}
        rules={[{ required: true, message: '请选择付款日期' }]}
        formItemConfig={{ className: cs('mr-20', 'mb-8', styles['plans__date']) }}
        placeholder='请选择收款日期'
      />
      <FormInputNumber
        name={['amount_plans', index, 'amount']}
        min={0}
        max={9999999999.99}
        config={{ addonAfter: '元', onChange: validateAmountPlans }}
        rules={[{ pattern: TWO_DECIMAL_NUMBER_REG, message: '请输入两位小数点小数', }]}
        formItemConfig={{ className: cs('mr-20', 'mb-8', styles['plans__amount']) }}
        placeholder='请输入收款金额'
      />
    </>}
  </Group>
</Form.Item>

Ref 组件实例

Vue

<PointEditor ref="pointEditor"/>

// 调用组件实例事件
this.$refs.pointEditor.init();

React

import { useRef } from 'react';

// 设置组件实例
const pointEditor = useRef(null);

// 调用组件实例事件
const ref = (pointEditor as any).current;
ref && ref.init(row);

<PointEditor ref={pointEditor}/>


// PointEditor 组件内抛出事件
// PointEditor.tsx
import { forwardRef, useImperativeHandle } from 'react';

const PointEditor:FC<any> = forwardRef(({ onConfirm }, ref) => {
  // 抛出给 ref 事件
  useImperativeHandle(ref, () => ({
    init
  }));
  
  const init = () => {};
});
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking