无缝切换?从Vue到React

本文主要针对Vue开发者或新手快速上手React。

本文主要分析Vue和React在开发上的区别,帮助Vue开发者快速上手React,同时也适用于前端新手入门React。

单文件组件 VS class组件 VS 函数组件

Vue: 单文件组件

javascript 复制代码
<template>
  <div>{{ greeting }} world</div>
</template>

<script>
  export default {
    data() {
      return {
        greeting: 'hello'
      }
    }
  }
</script>
<style>
</style>

React: Class组件

javascript 复制代码
class Comp extends Component {
  constructor(props) {
    super(props);
    this.state = {greeting: 'hello'};
  }
  
  render() {
    return (
      <div>
        <div>{ greeting } world</div>
      </div>
    );
  }
}

React: 函数组件(推荐)

在Vue单文件组件和React的Class组件中,我们的元素、数据变量等必须放到固定的位置,以一种固定的格式书写,而在函数组件中书写方式变得更简单,我们可以像写函数一样写组件。更重要的是,这样就不用关心那些难理解的this了。

javascript 复制代码
const Comp = () => {
  const [greeting, setGreeting] = useState('hello');
  
  return (
    <div>
      <div>{ greeting } world</div>
    </div>
  )
}

双向绑定 VS 单向数据流

在Vue中我们使用v-bind、v-modal对数据进行绑定,无论是来自用户操作导致的变更,还是在某个方法里赋值都能够直接更新数据,不需要手动进行update操作。

javascript 复制代码
this.data.greeting = "Hello"

而在React里需要调用set方法更新,当React感应到set触发时会再次调用render对dom进行刷新,虽然有些麻烦但这种方式可以让数据变化更加清晰易追寻。

javascript 复制代码
this.state.greeting = "Hello" // 错误写法

this.setState({greeting: 'Hello'}); // 正确写法✅
setGreeting('Hello'); // 来自hooks的set写法 后面会介绍

React的大buff:JSX

初次接触JSX的开发者可能会感觉JSX结构混乱,因为你可以在dom元素内部直接写js逻辑,也可以在js逻辑里写dom,这就像把html和js混在一起:

javascript 复制代码
import getUserType from './getUserType'

const Comp = () => {
  const [greeting, setGreeting] = useState('hello');
  
  const Button = () => {
    const userType = getUserType()
    
    if(userType === 0) {
      return <button>去购买</button>
    }   
    
    if(userType === 1) {
      return <button>去充值</button>
    } 
    
    return null
  }
  
  return (
    <div>
      <div>{ greeting } world</div>
      {Button()}
    </div>
  )
}

虽然元素和逻辑的边界模糊了,但我们的组件会变得更加灵活,这样能够将一个组件分成不同的模块,当需要修改是时我们只需关注对应的函数,不用担心影响到其他部分,这对复杂的页面结构非常有用。

Hooks

是什么

上面我们在讲数据流的时候有提到,处理数据的两种方式

javascript 复制代码
// 方式1
this.state = {greeting: 'Hello'}
this.setState({greeting: 'Hello'}); 

// 方式2
const [greeting, setGreeting] = useState('hello');
setGreeting('Hello');

其中第二种方式的useState就是Hooks中的一种,是比较常用的Hook,除此之外还有useEffect,useRef等,每个都有着不同的功能。

为什么用

逻辑独立

以数据更新为例,简单来讲,如果不用Hooks,每次更新数据都用setSate,我们的代码里就会出现很多setState调用,setState根据入参可以一次修改一个字段,也可以一次修改多个字段,想要知道某个数据在哪里被做了怎样的修改就会变的很麻烦,甚至可能不小心多写一个字段修改了不该修改的数据。而用Hooks的useState的话,因为它在定义时会对字段创建其专用的修改函数,所以只要有这个函数的调用,就代表这个字段被做了修改。

怎么用

常用Hooks(Hooks只能在的函数组件内使用):

1、useState: 用于定义组件的 State,相当于this.state=xxx或者Vue里的data(){return xxx}

javascript 复制代码
const [greeting, setGreeting] = useState('hello'); // greeting 默认 hello

// 点击greeting变为Hello1
<div onClick={setGreeting('Hello1')}>{greeting}</div>

2、useEffect:通过依赖变更触发的钩子函数 ,类似Vue的watcher

javascript 复制代码
// 当userId变化时调用refresh
useEffect(() => {
  refresh();
}, [userId]);

// 进入页面时会执行init, 退出时会执行destroy
useEffect(() => {
  init();
  
  return () => {
     destroy()
  }
}, []);

3、useRef: 返回ref对象,.current可以获取到其原生的元素本身

javascript 复制代码
const el = useRef(null);

<div ref={el}></div>

// console.log(el.current.offsetHeight) 返回div的offsetHeight

状态管理

是什么?为什么用?

官方定义:"集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化"。

举个例子,页面里两个组件需要展示/更新userName,如果不使用状态管理,我们可以用父子组件交互的方式把userName字段和setUserName函数作为组件参数传入两个组件中,调用setUserName即触发page更新userName:

但当业务变得越来越复杂,就会陷入透传地狱!

加入状态管理后,不在涉及组件之间的参数传递,数据管理全部放在Store中管理,组件直接从Store中读取数据,修改时调用Store的修改函数修改对应数据:

怎么用

Vue:Vuex

在Vue中,官方脚手架自带Vuex为我们注入好了Store,常用的state负责定义数据,mutations负责修改数据,actions负责利用mutations做一些复杂异步操作(如接口请求)

javascript 复制代码
// store.js
import { createStore } from 'vuex'
const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    setCount (state, value) {
      state.count = value
    }
  },
  actions: {
    addon ({ commit, state }) {
      const count = state.count
      commit('set', count+1)
    }
  }
})
javascript 复制代码
// index.js
import App from './vue'
import { createApp } from 'vue'

const app = createApp(App).mount('#app');

// 将 store 实例作为插件安装
app.use(store)

// index.vue
<template>
  <div>{{ this.$store.state.count }} world</div>
</template>

<script>
  export default {
    methods: {
      increment() {
        this.$store.commit('setCount', 10)
        this.$store.dispatch('setCount')
        console.log(this.$store.state.count)
      }
    }
  }
</script>
<style>
</style>

React:不止是Redux

React本身并不带状态管理,状态管理对于React更像是一种普通的第三方工具,工作中不同项目可能用了Redux、mobx、rematch等不同的状态管理工具,不同工具写法会有所区别,使用者要自己区分学习,除此之外一些脚手架会自带状态管理,写法会简单些,比如Rax,为方便理解接下来以Rax的写法进行说明。

与上面所说的Vuex的state、mutations、actions对应,React里叫做state、reducers、effects。state负责定义数据,reducers负责修改数据,effects负责利用reducers做一些复杂异步操作,下面示例解释的更清楚:

javascript 复制代码
// src/pages/Dashboard/models/counter.ts
const delay = (time) => new Promise((resolve) => setTimeout(() => resolve(), time));

export default {
  // 定义 model 的初始 state
  state: {
    count: 0
  },
  // 定义改变该模型状态的纯函数
  reducers: {
    increment(prevState) {
      return { count: prevState.count + 1 };
    },
  },
  effects: (dispatch) => ({
    async incrementAsync() {
      await delay(10);
      dispatch.counter.increment();
    },
  }),
}
javascript 复制代码
// src/pages/Dashboard/store.ts
import { createStore } from 'rax-app';
import counter from './models/counter';

const store = createStore({ counter });

export default function Dashboard() {
  // 使用 counter 模型
  const [counterState, counterDispatchers] = store.useModel('counter');

  return (
    <>
      <span>{counterState.count}</span>
      <button onClick={counterDispatchers.increment}>+</button>
      <button onClick={counterDispatchers.incrementAsync}>+</button>
    </>
  );
}

React代码实战:开发一个TodoList

javascript 复制代码
// index.jsx
import $i18n from '@alife/panda-i18n';
import React, { useCallback } from 'react';
import { connect } from 'react-redux';
import { Link } from '@ice/router';
import PropTypes from 'prop-types';
import { Form, Input } from 'cn-next';
import styles from './index.module.scss';

const FormItem = Form.Item;

const AddTodo = (props) => {
  const { onAdd } = props;
  const onSubmit = useCallback(
    (values, errors) => {
      if (!errors) {
        onAdd(values.text);
      }
    },
    [onAdd],
  );

  return (
    <div x-class={[styles.add]}>
      <Form className={styles.addForm} inline onSubmit={onSubmit}>
        <FormItem
          className={styles.addItem}
          required
          requiredMessage={$i18n.get({
            id: 'EnterAToDoList.other',
            dm: '请输入待办事项',
          })}
        >
          <Input
            name='text'
            placeholder={$i18n.get({
              id: 'EnterAToDoList.other',
              dm: '请输入待办事项',
            })}
          />
        </FormItem>
        <Form.Submit className={styles.addSubmit} onClick={onSubmit} validate>
          {$i18n.get({ id: 'Add.other', dm: '添加' })}
        </Form.Submit>
      </Form>
    </div>
  );
};

AddTodo.propTypes = {
  onAdd: PropTypes.func,
};

AddTodo.defaultProps = {
  onAdd: () => {},
};

const Todos = (props) => {
  const { list, createAsync } = props;

  // 添加
  const onAddTodo = useCallback(
    async (text) => {
      await createAsync(text);
    },
    [createAsync],
  );

  return (
    <div className={styles.todos}>
      <AddTodo onAdd={onAddTodo} />
      <div className='mb-30'>
        {list.map((item) => {
          return (
            <div key={item.text} className={styles.todo}>
              <span>{item.text}</span>
            </div>
          );
        })}
      </div>
      <div>
        <Link to='/'>
          {$i18n.get({ id: 'ReturnToHomePage.other', dm: '返回首页' })}
        </Link>
      </div>
    </div>
  );
};

Todos.propTypes = {
  list: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
  createAsync: PropTypes.func.isRequired,
};

const mapState = (state) => ({
  list: state.todos.list,
});

const mapDispatch = (dispatch) => ({
  createAsync: dispatch.todos.createAsync,
  doneAsync: dispatch.todos.doneAsync,
  undoneAsync: dispatch.todos.undoneAsync,
});

export default connect(mapState, mapDispatch)(Todos);
javascript 复制代码
// todo.js
export const todos = {
  state: {
    list: [
      {
        text: 'Learn typescript',
        done: true,
      },
      {
        text: 'Try immer',
        done: false,
      },
    ],
  },
  reducers: {
    create(state, text) {
      state.list.push({ text, done: false });
      return state;
    },
    done(state, idx) {
      if (state.list[idx]) {
        state.list[idx].done = true;
      }
      return state;
    },
    undone(state, idx) {
      if (state.list[idx]) {
        state.list[idx].done = false;
      }
      return state;
    },
  },
  effects: (dispatch) => ({
    async createAsync(payload) {
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.create(payload);
    },
    async doneAsync(payload) {
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.done(payload);
    },
    async undoneAsync(payload) {
      // 模拟异步操作
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.undone(payload);
    },
  }),
};
相关推荐
大雷神10 分钟前
HarmonyOS APP<玩转React>开源教程二十二:每日一题功能
前端·react.js·开源·harmonyos
技术钱11 分钟前
vue3基于 Vxe Table 实现可拖拽分组 + 动态求和的高级表格
javascript·vue.js
还是大剑师兰特12 分钟前
Vue3 + Element Plus 日期选择器:开始 / 结束时间,结束时间不超过今天
前端·javascript·vue.js
不会写DN12 分钟前
Js常用数组处理
开发语言·javascript·ecmascript
还是大剑师兰特14 分钟前
数组中有两个数据,将其变成字符串
开发语言·javascript·vue.js
Saga Two14 分钟前
Vue实现核心原理
前端·javascript·vue.js
技术钱15 分钟前
vue3实现时间根据系统时区转换对应的时间
javascript·vue.js
殷忆枫21 分钟前
基于STM32的ML307R连接Onenet平台
服务器·前端·javascript
Java 码农23 分钟前
vue cli 环境搭建
前端·javascript·vue.js
问道飞鱼23 分钟前
【前端知识】使用React+Vite构建企业级项目模板
前端·react.js·前端框架·vite