By Mengya Li (Miya)
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.
<template>
<div>{{ greeting }} world</div>
</template>
<script>
export default {
data() {
return {
greeting: 'hello'
}
}
}
</script>
<style>
</style>
class Comp extends Component {
constructor(props) {
super(props);
this.state = {greeting: 'hello'};
}
render() {
return (
<div>
<div>{ greeting } world</div>
</div>
);
}
}
Official documentation [1]
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]
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.
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.
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.
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.
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.
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:
However, as the business becomes more and more complex, you will fall into the hell of pass-through!
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:
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 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>
</>
);
}
// 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);
},
}),
};
[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.
1,042 posts | 256 followers
FollowAlibaba F(x) Team - June 20, 2022
XianYu Tech - August 10, 2021
Alibaba Clouder - May 27, 2019
Alibaba F(x) Team - October 9, 2021
Alibaba Cloud Community - April 20, 2022
Alibaba Developer - January 21, 2021
1,042 posts | 256 followers
FollowA low-code development platform to make work easier
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreAlibaba Cloud (in partnership with Whale Cloud) helps telcos build an all-in-one telecommunication and digital lifestyle platform based on DingTalk.
Learn MoreMore Posts by Alibaba Cloud Community