×
Community Blog Seamless Switching? From Vue to React

Seamless Switching? From Vue to React

This article primarily analyzes the differences between Vue and React in development, to help Vue developers and front-end beginners quickly get started with React.

By Mengya Li (Miya)

1

This article primarily analyzes the differences between Vue and React in development, to help Vue developers and front-end beginners quickly get started with React.

Single-file Component vs. Class Component vs. Function Component

Vue: Single-file Component

<template>
  <div>{{ greeting }} world</div>
</template>

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

React: Class Component

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

Official documentation [1]

React: Function Component (Recommended)

In the single-file component of Vue and the class component of React, elements and data variables must be placed in a fixed position and written in a fixed format. However, in the function component, the writing is simpler, allowing you to write the component as if you are writing a function. More importantly, you don't have to worry about those hard-to-understand this references.

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

Official documentation [1]

Two-way Binding vs. One-way Data Flow

In Vue, data can be bound using v-bind and v-model. Whether it is a change caused by user operations or a value assignment in a method, the data can be directly updated without manual operations.

this.data.greeting = "Hello"

In React, you need to call the set method to update. When React detects that the set is triggered, it will call render again to refresh the DOM. Although this method is more complex, data changes are clearer and easier to trace.

this.state.greeting = "Hello" // Invalid.

this.setState({greeting: 'Hello'}); // Valid.✅
setGreeting('Hello'); // The method of writing set from hooks, which will be described later.

A Big Buff for React: JSX

Developers who are first introduced to JSX may find its structure disordered. You can write JS logic directly in DOM elements or write DOM elements directly in JS logic, which is like mixing HTML with JS:

import getUserType from './getUserType'

const Comp = () => {
  const [greeting, setGreeting] = useState('hello');
  
  const Button = () => {
    const userType = getUserType()
    
    if(userType === 0) {
      return <button>Buy Now</button>
    }   
    
    if(userType === 1) {
      return <button>Top Up Now</button>
    } 
    
    return null
  }
  
  return (
    <div>
      <div>{ greeting } world</div>
      {Button()}
    </div>
  )
}

Although the boundary between elements and logic is blurred, the components are more flexible. In this way, you can divide a component into different modules. When you need to modify the component, you only need to pay attention to the corresponding function without worrying about affecting other parts. This is useful for complex page structures.

Hooks

What Are Hooks?

We mentioned in the data flow section that there are two methods to process data:

// Method 1
this.state = {greeting: 'Hello'}
this.setState({greeting: 'Hello'});

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

The useState in the second method is a type of hook that is more commonly used. In addition to useState, there are useEffect and useRef, each having different features.

Why Use Hooks?

Logically Independent

Take data update as an example. In short, if you use setSate instead of hooks for each data update, there will be many setState calls in your code. setState can modify one field or multiple fields at a time according to the input parameters. Therefore, it will be very troublesome if you want to know where certain data has been modified and how it has been modified. setState may even accidentally write an extra field which makes undesired modifications in data. This is not the case with the useState hook. A dedicated modification function for the field is created when the useState is defined, so a call to this function indicates that the field has been modified.

How to Use Hooks?

Hooks can be used only in function components. The following is the usage of common hooks:

1) useState: Used to define the state of a component, which is equivalent to this.state=xxx or Vue's data(){return xxx}.

const [greeting, setGreeting] = useState('hello'); // The default value of greeting is hello.

// Click greeting to change the value to Hello1.
<div onClick={setGreeting('Hello1')}>{greeting}</div>

2) useEffect: A hook function triggered by dependency change, similar to Vue's watcher.

// Call refresh when the userId changes.
useEffect(() => {
  refresh();
}, [userId]);

// Execute Init when you enter the page, and execute destroy when you exit the page.
useEffect(() => {
  init();
  
  return () => {
     destroy()
  }
}, []);

3) useRef: Returns a ref object. .current can obtain its native element.

const el = useRef(null);

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

// console.log(el.current.offsetHeight) Return the offsetHeight of the div.

Status Management

What is Status Management? Why Use Status Management?

According to official definition, status management "provides a centralized and predictable way to manage states of all components, making it easier to debug and reason about state changes".

For example, consider two components in the page that need to display and update userName. If you do not use status management, you can pass the userName field and the setUserName function as component parameters into the two components in the way of parent-child component interaction, and then call setUserName to trigger the page to update userName:

2

However, as the business becomes more and more complex, you will fall into the hell of pass-through!

3

By contrast, if you add status management, parameter passing between components is not involved. All data is managed in your store. Components directly read data from the store, and call the store's modification function to modify the corresponding data:

4

How to Use Status Management?

Vue: Vuex

In Vue, the official Vuex scaffold helps you to inject the store into components. Commonly used ones include state to define data, mutationsto modify data, and actions which uses mutations for some complex asynchronous operations such as interface requests.

// 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)
    }
  }
})
// index.js
import App from './vue'
import { createApp } from 'vue'

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

// Install the store instance as a plug-in.
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: Beyond Redux

React itself does not come with state management. For React, state management is more like a regular third-party tool. Different projects may use different state management tools, such as Redux, MobX, and Rematch, and the syntax for each tool may vary. Users need to differentiate and learn how to use them. In addition, some scaffolds come with their own state management, making it simpler to write, such as Rax. For the purpose of illustration, the following explanation will use Rax. Corresponding to the state, mutations, and actions in Vuex, React uses state, reducers, and effects. The state is responsible for defining data, reducers are responsible for modifying data, and effects are responsible for using reducers to perform complex asynchronous operations. The following example will provide a clearer explanation:

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

export default {
  // Define the initial state of the model.
  state: {
    count: 0
  },
  // Define a pure function that changes the state of the model.
  reducers: {
    increment(prevState) {
      return { count: prevState.count + 1 };
    },
  },
  effects: (dispatch) => ({
    async incrementAsync() {
      await delay(10);
      dispatch.counter.increment();
    },
  }),
}
// src/pages/Dashboard/store.ts
import { createStore } from 'rax-app';
import counter from './models/counter';

const store = createStore({ counter });

export default function Dashboard() {
  // Use the counter model.
  const [counterState, counterDispatchers] = store.useModel('counter');

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

The Practice of React Code: Develop a TodoList

// 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: 'Please enter the to-do list',
          })}
        >
          <Input
            name='text'
            placeholder={$i18n.get({
              id: 'EnterAToDoList.other',
              dm: 'Please enter the to-do list',
            })}
          />
        </FormItem>
        <Form.Submit className={styles.addSubmit} onClick={onSubmit} validate>
          {$i18n.get({ id: 'Add.other', dm: 'Add' })}
        </Form.Submit>
      </Form>
    </div>
  );
};

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

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

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

  // Add
  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: 'Return to homepage' })}
        </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);
// 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) {
      // Simulate asynchronous operations.
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.create(payload);
    },
    async doneAsync(payload) {
      // Simulate asynchronous operations.
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.done(payload);
    },
    async undoneAsync(payload) {
      // Simulate asynchronous operations.
      await new Promise((resolve) => setTimeout(resolve, 250));
      dispatch.todos.undone(payload);
    },
  }),
};

References

[1] https://legacy.reactjs.org/docs/components-and-props.html#function-and-class-components

Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.

0 1 0
Share on

Alibaba Cloud Community

875 posts | 198 followers

You may also like

Comments