React & Redux 对比 Vue & Vuex 使用分享

本文对比了React与Vue的使用,探讨了React的元素、组件、State管理和事件处理,以及Vue的template和组件。接着,文章对比了Vuex与Redux的状态管理,解释了Action、Reducer和Store的概念。最后,介绍了React-Redux的Provider和connect函数,讨论了如何通过它们连接React组件和Redux store。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文旨在帮助 Vue 方向的同学了解 React 用法,所以侧重两者对比。而且是根据我的实践选取了一些我认为需要注意或重点讲解的内容,不涉及深层原理。文中没有提到基本语法和核心 API 建议阅读文档,见下文。

如果没有使用过 Vue 的同学建议直接阅读文档进行学习:
React 中文文档
Redux 英文文档
React-Redux 英文文档

希望对你有帮助,那我们开始吧

一、Vue & React 对比

Vue

template

<template>
  <p>{{ greeting }} World!</p>
</template>

<script>
module.exports = {
  data: function() {
    return {
      greeting: "Hello"
    };
  }
};
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

这是一段 vue 单文件组件的代码,其中 template 模版编译的过程是:vue-template-compiler 将 template 预编译为 render functions 后注入到从 <script> 导出的组件中。

Created with Raphaël 2.2.0 vue-template-compiler render functions <script> 导出组件

Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。
然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。
比如使用 iView 的 Table 组件渲染按钮(从 3.2.0 版本开始支持 slot-scope 写法):

{
   title: 'Action',
   key: 'action',
   width: 150,
   align: 'center',
   render: (h, params) => {
       return h('div', [
           h('Button', {
               props: {
                   type: 'primary',
                   size: 'small'
               },
               style: {
                   marginRight: '5px'
               },
               on: {
                   click: () => {
                       this.show(params.index)
                   }
               }
           }, 'View'),
           h('Button', {
               props: {
                   type: 'error',
                   size: 'small'
               },
               on: {
                   click: () => {
                       this.remove(params.index)
                   }
               }
           }, 'Delete')
       ]);
   }
}

渲染函数和JSX可以参考这两篇文章:
终于搞懂了vue 的 render 函数(一) -_-|||
终于搞懂了vue 的 render 函数(二)(๑•̀ㅂ•́)و✧

React

在 React 中,所有的组件的渲染功能都依靠 JSX。Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

元素

元素是构成 React 应用的最小砖块。描述了你在屏幕上想看到的内容。

// React 元素
const element = <h1>Hello, world</h1>;
// React 自定义元素
const element = <Welcome name="Sara" />;

组件

从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

React 元素可以简单理解为是用 <></> 包起来的,而组件返回的是元素。

简单来说组件返回的都是我们即将渲染在页面上的内容,我们的页面就是一个个组件组合起来的,这个概念跟 Vue 是相通的。

props 在组件内部可以直接使用,需要注意:props 是只读的,不能修改。

函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
class 组件

ES6 的 class

  • 使用 class 创建组件的时候需要继承 React.Component
  • class 内部一定要有一个 render() 函数返回要渲染的元素
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
渲染组件
ReactDOM.render(
  element,
  document.getElementById('root')
);

State

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。

state 用于标记当前组件的状态,其他组件不能使用。

  • 只有 class 组件可以使用 state
  • 构造函数是唯一可以给 this.state 赋值的地方
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

正确使用 State

  • 不要直接修改 state,应该使用 this.setState()
    // Wrong
    this.state.comment = 'Hello';
    // Correct
    this.setState({comment: 'Hello'});
    
  • state 的更新可能是异步的,所以你不要依赖他们的值来更新下一个状态。
    // Wrong
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    // Correct
    this.setState((state, props) => ({
      counter: state.counter + props.increment
    }))
    

组合

React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

<FancyBorder> JSX 标签中的所有内容都会作为一个 children prop 传递给 FancyBorder 组件。因为 FancyBorder{props.children} 渲染在一个 <div> 中,被传递的这些子组件最终都会出现在输出结果中。

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

<Contacts /><Chat /> 之类的 React 元素本质就是对象(object),所以你可以把它们当作 props,像其他数据一样传递。这种方法可能使你想起别的库中“槽”(slot)的概念,但在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递

事件处理

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理 有关。

class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}

二、Vuex & Redux 对比

Vuex

一个简单的 store

可以看到 Vuex 的 store 结构是比较清晰的,包含:

  • state
  • getters
  • mutations
  • actions
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    double: state => {
      return state.count * 2
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

工作流程

在这里插入图片描述

Redux

这里主要是结合文档梳理下核心概念和 Redux 工作流程。
在这里插入图片描述
当用户操作 view 之后发出一个 action,store 会根据这个 action 的 type 来遍历所有的 reducers 找到对应的 reducer 来改变 state。

核心概念

假设你的 state 结构是这样的:

{
  todos: [{
    text: 'Eat food',
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED'
}

这看起来跟普通对象没有什么不同,不过不能通过直接赋值来改变 state。

Action

Action 是把数据从应用传到 store 的有效载荷。是 store 数据的唯一来源。通过 store.dispatch() 将 action 传到 store。

  • action 是改变 state 的唯一方式;
  • action 对象必须有 type 属性,用来描述动作类型;
  • type 通常被定义为常量;

Action 创建函数:就是创建 action 的函数。在 Redux 中,action 创建函数只是返回一个 action 对象。这样做使 action 方便使用且易于测试。

actions.js

/*
 * action types
 */

export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'

/*
 * other constants
 */

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

/*
 * action creators
 */

export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function toggleTodo(index) {
  return { type: TOGGLE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

也许你会觉得这跟 Vuex 中的 mutation 有点像,但实际上 action 只是描述了执行哪些操作,并没有描述具体怎么执行的。如何改变 state 的代码是在 reducer 中。

Reducer

(previousState, action) => nextState Reducer 描述了如何更新 state。

  • 每个 reducer 都在管理全局 state 中自己负责的部分;
  • 每个 reducer 的 state 参数都不同,并且对应于它管理的那部分 state 数据。

比如 visibilityFilter reducer 的 state 参数对应的是 state.visibilityFilter,而 todos reducer 的 state 参数对应的 则是 state.todos

reducers.js

import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

⚠️ reducer 是纯函数,不能有以下操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now()Math.random()

⚠️ 注意

  • Redux 首次执行时 state 为 undefined,可以 reducer 里初始化。
  • 不要直接修改 state。而是使用 Object.assign() 新建一个副本,或者使用 { ...state, ...newState } 达到相同的目的。
  • 遇到未知的 action 时,一定要返回旧的 state。
combineReducers()

随着应用变得越来越复杂,可以考虑将 reducer 函数拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。
combineReducers 辅助函数的作用是,把多个 reducer 合并成一个。

const rootReducer = combineReducers(reducers)

参数:reducers 是一个对象,对象的值是各个 reducer 函数。
返回值:是一个函数。调用参数里所有 reducer 的函数(这个返回的函数也是一个 reducer),并且构造一个与 reducers 对象结构相同的 state 对象,也就是全局 state。

Store

Store 是把 action 和 reducer 联系到一起的对象。Store 有以下职责:

  1. 维持应用的 state;
  2. 提供 getState() 方法获取 state;
  3. 提供 dispatch(action) 方法更新 state;
  4. 通过 subscribe(listener) 注册监听器;
  5. 通过 subscribe(listener) 返回的函数注销监听器。

createStore 用来创建一个 Redux store 来以存放应用中所有的 state。

const store = createStore(reducer, [preloadedState], enhancer)

index.js

import { createStore } from 'redux'
import todoApp from './reducers'

const store = createStore(todoApp)

组件可以访问 Redux store,通过把它以 props 的形式传入。但这样一层层的传递既繁琐又容易出错。

ReactDOM.render(
	<App store={store} />,
	document.getElementById('root')
)

三、React-Redux

Redux 本身是一个独立的库,可以与任何UI层或框架一起使用,包括React,Angular,Vue,Ember和vanilla JS。

React-Redux 是 Redux 的官方 React 绑定库。它能够使你的 React 组件从 Redux store 中读取数据,并且向 store 分发 actions 以更新数据。

Provider

<Provider/> 组件是 React-Redux 提供的一个 API,能够使其子组件访问 Redux store。
通常在渲染根组件时使用。

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'

import { App } from './App'
import createStore from './createReduxStore'

const store = createStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

connect

connect() 是 React-Redux 提供的另一个 API,作用是连接 React 组件 和 Redux store。使组件能够访问 state,dispatch actions。

import { login, logout } from './actionCreators'

const mapState = state => state.user
const mapDispatch = { login, logout }

// call connect to generate the wrapper function, and immediately call
// the wrapper function to generate the final wrapper component.

export default connect(
  mapState,
  mapDispatch
)(Login)

connect() 返回的是一个包装函数。该包装函数接收你的组件,并返回一个被传入了额外 props 的组件。
额外的 props 就是通过 connect() 访问到的 state。

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

这里只讲解前两个参数: mapStateToPropsmapDispatchToProps

mapStateToProps

mapStateToProps 用于从 store 中选择被 connect 的组件所需的数据。

function mapStateToProps(state, ownProps?)
  • 参数
  1. state:Redux store 的完整 state,每次 state 更改时都会调用 mapStateToProps
  2. ownProps:可选参数,被 connect 的组件自身的 props
  • 返回值
    是一个普通对象,包含被 connect 的组件所需要的数据。该对象中的字段将作为 props 合并到被 connect 的组件。

mapDispatchToProps

mapDispatchToProps is used for dispatching actions to the store.

使用 store.dispatch 可以 dispatch 一个 action,这是更改 state 的唯一方法。使用了 React-Redux 之后,我们的组件无需直接访问 store,connect 会帮我们访问。

connect 封装了与 Redux store 对话的逻辑,我们只需要使用它提供的API就行了。

有两种方式可以 dispatch action

第一种是默认方式,第二种要用到我们接下来要说的 mapDispatchToProps 参数。

一、默认(不给 connect 传 mapDispatchToProps 参数)情况下,被 connect 的组件会接收 props.dispatch 来 dispatch action。

connect()(MyComponent)
// which is equivalent with
connect(
  null,
  null
)(MyComponent)

// or
connect(mapStateToProps /** no second argument */)(MyComponent)

使用:

function Counter({ count, dispatch }) {
  return (
    <div>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>reset</button>
    </div>
  )
}

二、另外 connect 可以接受 mapDispatchToProps 参数来创建 dispatch 函数并将这些函数作为 props 传递给组件。

⚠️ 如果给 connect 传递了 mapDispatchToProps 参数,则被 connect 的组件将不再接收 props.dispatch

mapDispatchToProps 也有两种形式

  1. 函数形式:允许更多的自定义,能够访问 dispatch,以及可选参数 ownProps

    const mapDispatchToProps = dispatch => {
      return {
        // dispatching plain actions
        increment: () => dispatch({ type: 'INCREMENT' }),
        decrement: () => dispatch({ type: 'DECREMENT' }),
        reset: () => dispatch({ type: 'RESET' })
      }
    }
    

    参数:

    • dispatch:store.dispatch
    • ownProps:可选参数,组件自身的 props

    返回值

    • 是一个普通对象。该对象的字段将作为 props 合并到被 connect 的组件的 props
    function Counter({ count, increment, decrement, reset }) {
      return (
        <div>
          <button onClick={decrement}>-</button>
          <span>{count}</span>
          <button onClick={increment}>+</button>
          <button onClick={reset}>reset</button>
        </div>
      )
    }
    

    此时的 increment, decrement, reset 还可以传递给子组件。

  2. 对象形式:更具说明性且易于使用。是函数形式的简写,connect 帮我们做了省略的操作。

    import {increment, decrement, reset} from "./counterActions";
    
    const mapDispatchToProps = {
      increment,
      decrement,
      reset
    }
    
    export default connect(mapState, mapDispatchToProps)(Counter);
    
    // or
    export default connect(
      mapState,
      { increment, decrement, reset }
    )(Counter);
    

异步 Action

Redux Thunk 是处理 Redux 异步逻辑推荐使用的中间件。Redux Thunk 中间件可以让你的 Action 创建函数返回一个函数而不是普通对象。

这里不展开说了,更多内容查看 Redux Thunk 文档

启用 Redux-Thunk 需要使用 applyMiddleware()

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(rootReducer, applyMiddleware(thunk));

// 异步操作网络请求
function fetchSecretSauce() {
  return fetch('https://www.google.com/search?q=secret+sauce');
}
// 普通 Action 创建函数:返回普通对象
function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce,
  };
}
// 普通 Action 创建函数:返回普通对象
function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error,
  };
}
// 异步 Action 创建函数:返回的是函数。此时的 Action 创建函数就成了 thunk action。
// 当 Action 创建函数返回函数时,这个函数会被 Redux Thunk 中间件执行。
function makeASandwichWithSecretSauce(forPerson) {
  return function(dispatch) {
    return fetchSecretSauce().then(
      (sauce) => dispatch(makeASandwich(forPerson, sauce)),
      (error) => dispatch(apologize('The Sandwich Shop', forPerson, error)),
    );
  };
}
import { connect } from 'react-redux';
import { Component } from 'react';

class SandwichShop extends Component {
  componentDidMount() {
    this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson));
  }

  componentDidUpdate(prevProps) {
    if (prevProps.forPerson !== this.props.forPerson) {
      this.props.dispatch(makeASandwichWithSecretSauce(this.props.forPerson));
    }
  }

  render() {
    return <p>{this.props.sandwiches.join('mustard')}</p>;
  }
}

export default connect((state) => ({
  sandwiches: state.sandwiches,
}))(SandwichShop);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值