📜  ReactJS 中的 Redux 和 Flux 有什么区别?

📅  最后修改于: 2022-05-13 01:56:39.467000             🧑  作者: Mango

ReactJS 中的 Redux 和 Flux 有什么区别?

在应用程序或软件开发阶段,我们收集客户的需求以创建解决方案来解决客户或业务的问题。为了解决问题,我们依赖于不同的技术和架构模式。长期以来,开发人员都在使用 MVC(模型-视图-控制器)模式。正如我们所知,每个月之后,都会有新的技术以新功能取代以前的功能进入市场。因此,Facebook 的开发团队以同样的方式提出了重要的更改和发布通量,这成为 MVC 架构的替代选择。在 Flux 之后,又出现了另一个框架 Redux 进入市场。让我们在本文中讨论 Redux 与 Flux。

Flux: Flux 是应用程序架构,或者我们可以说 JavaScript 架构,用于构建客户端 Web 应用程序或客户端应用程序的 UI。您无需大量新代码即可开始使用通量。 Flux 克服了 MVC 的缺点,例如不稳定性和复杂性。

示例:在此示例中,我们创建了一个 TODO 列表应用程序。此示例包含可以在 TODO 列表中添加任务以及删除任务列表的功能。

第 1 步:使用以下命令创建一个新的 react 应用程序。

第 2 步:在您的代码编辑器中创建一个文件夹结构,如下面的屏幕截图所示,您可以手动创建文件,也可以使用命令创建文件。

项目结构:

todo应用文件夹结构

第 3 步(操作):操作是在应用程序生命周期内发生的事情。在我们的应用程序中,当用户单击创建按钮时,将调用一个函数CRAETE_TODO ,并将一个新任务添加到列表中。单击删除按钮时,相同的DELETE_TODO函数将执行删除操作。这是 Action 组件的一个示例。

TodoActions.js
import dispatcher from "../dispatcher";
  
/* Create task function */
export function createTodo(text) {
  dispatcher.dispatch({
    type: "CREATE_TODO",
    text,
  });
}
  
/* Delete task function */
export function deleteTodo(id) {
  dispatcher.dispatch({
    type: "DELETE_TODO",
    id,
  });
}


dispatcher.js
import { Dispatcher } from "flux";
  
export default new Dispatcher;


Javascript
import { EventEmitter } from 'events';
  
import dispatcher from '../dispatcher';
  
class TodoStore extends EventEmitter {
  constructor() {
    super();
  
    this.todos = [
      {
        id: 16561,
        text: 'hello'
      },
      {
        id: 16562,
        text: 'another todo'
      },
    ];
  }
  
  createTodo(text) {
    const id = Date.now();
  
    this.todos.push({
      id,
      text
    });
  
    this.emit('change');
  }
  
  deleteTodo(id){
    this.todos = this.todos.filter((elm)=>{
      return (elm.id != id);
    });
    this.emit('change');
  }
  
  getAll() {
    return this.todos;
  }
  
  handleActions(action) {
    switch (action.type) {
      case 'CREATE_TODO': {
        this.createTodo(action.text);
        break;
      }
      case 'DELETE_TODO': {
        this.deleteTodo(action.id);
        break;
      }
    }
  }
}
  
const todoStore = new TodoStore();
dispatcher.register(todoStore.handleActions.bind(todoStore));
export default todoStore;


Todolist.js
import React from 'react';
import Todo from '../components/Todo';
import TodoStore from '../stores/TodoStore.js';
import * as TodoActions from '../actions/TodoActions';
  
export default class Todolist extends React.Component {
  constructor() {
    super();
  
    this.state = {
      todos: TodoStore.getAll(),
    };
    this.inputContent = '';
  }
  
  // We start listening to the store changes
  componentWillMount() {
   TodoStore.on("change", () => {
     this.setState({
       todos: TodoStore.getAll(),
     });
   });
  }
  
  render() {
      
    const TodoComp = this.state.todos.map(todo => {
      return ;
    });
  
    return (
      
        

GFG Todo list

        this.inputContent = evt.target.value} />                  
    {TodoComp}
      
    );   } }


Todo.js
import React from "react";
import * as TodoActions from '../actions/TodoActions';
  
export default class Todo extends React.Component{
    
  render(){
    return(
      
  •           {this.props.text}                
  •               );    } }


    index.js
    import React from 'react';
    import { render } from 'react-dom';
    import Todolist from './pages/Todolist';
      
    const app = document.getElementById('root');
      
    // render(React.createElement(Layout), app);
    render(, app);


    Counter.js
    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
      
    class Counter extends Component {
      constructor(props) {
        super(props);
        this.incrementAsync = this.incrementAsync.bind(this);
        this.incrementIfOdd = this.incrementIfOdd.bind(this);
      }
      
      incrementIfOdd() {
        if (this.props.value % 2 !== 0) {
          this.props.onIncrement()
        }
      }
      
      incrementAsync() {
        setTimeout(this.props.onIncrement, 1000)
      }
      
      render() {
        const { value, onIncrement, onDecrement } = this.props
        return (
            
          

            

    GeeksForGeeks Counter Example

            Clicked: {value} times         {' '}                  {' '}                  {' '}                  {' '}                

        )   } }    Counter.propTypes = {   value: PropTypes.number.isRequired,   onIncrement: PropTypes.func.isRequired,   onDecrement: PropTypes.func.isRequired }    export default Counter


    Counter.spec.js
    import React from 'react'
    import { shallow } from 'enzyme'
    import Counter from './Counter'
      
    function setup(value = 0) {
      const actions = {
        onIncrement: jest.fn(),
        onDecrement: jest.fn()
      }
      const component = shallow(
        
      )
      
      return {
        component: component,
        actions: actions,
        buttons: component.find('button'),
        p: component.find('p')
      }
    }
      
    describe('Counter component', () => {
      it('should display count', () => {
        const { p } = setup()
        expect(p.text()).toMatch(/^Clicked: 0 times/)
      })
      
      it('first button should call onIncrement', () => {
        const { buttons, actions } = setup()
        buttons.at(0).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('second button should call onDecrement', () => {
        const { buttons, actions } = setup()
        buttons.at(1).simulate('click')
        expect(actions.onDecrement).toBeCalled()
      })
      
      it('third button should not call onIncrement if the counter is even', () => {
        const { buttons, actions } = setup(42)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).not.toBeCalled()
      })
      
      it('third button should call onIncrement if the counter is odd', () => {
        const { buttons, actions } = setup(43)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('third button should call onIncrement if the counter is
        odd and negative', () => {
        const { buttons, actions } = setup(-43)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('fourth button should call onIncrement in a second', (done) => {
        const { buttons, actions } = setup()
        buttons.at(3).simulate('click')
        setTimeout(() => {
          expect(actions.onIncrement).toBeCalled()
          done()
        }, 1000)
      })
    })


    index.js
    export default (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1
        case 'DECREMENT':
          return state - 1
        default:
          return state
      }
    }


    Index.spec.js
    import counter from './index'
      
    describe('reducers', () => {
      describe('counter', () => {
        it('should provide the initial state', () => {
          expect(counter(undefined, {})).toBe(0)
        })
      
        it('should handle INCREMENT action', () => {
          expect(counter(1, { type: 'INCREMENT' })).toBe(2)
        })
      
        it('should handle DECREMENT action', () => {
          expect(counter(1, { type: 'DECREMENT' })).toBe(0)
        })
      
        it('should ignore unknown actions', () => {
          expect(counter(1, { type: 'unknown' })).toBe(1)
        })
      })
    })


    index.js
    import React from 'react'
    import ReactDOM from 'react-dom'
    import { createStore } from 'redux'
    import Counter from './components/Counter'
    import counter from './reducers'
      
    const store = createStore(counter)
    const rootEl = document.getElementById('root')
      
    const render = () => ReactDOM.render(
       store.dispatch({ type: 'INCREMENT' })}
        onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
      />,
      rootEl
    )
      
    render()
    store.subscribe(render)


    第 4 步:调度员

    将 Dispatcher 视为路由器。动作被发送给调用适当回调的调度程序。

    调度程序.js

    import { Dispatcher } from "flux";
      
    export default new Dispatcher;
    

    第 5 步(商店):

    商店是保存应用程序状态的地方。一旦你有了reducer,就很容易创建一个store。我们将 store 属性传递给 provider 元素,它包装了我们的路由组件。

    Javascript

    import { EventEmitter } from 'events';
      
    import dispatcher from '../dispatcher';
      
    class TodoStore extends EventEmitter {
      constructor() {
        super();
      
        this.todos = [
          {
            id: 16561,
            text: 'hello'
          },
          {
            id: 16562,
            text: 'another todo'
          },
        ];
      }
      
      createTodo(text) {
        const id = Date.now();
      
        this.todos.push({
          id,
          text
        });
      
        this.emit('change');
      }
      
      deleteTodo(id){
        this.todos = this.todos.filter((elm)=>{
          return (elm.id != id);
        });
        this.emit('change');
      }
      
      getAll() {
        return this.todos;
      }
      
      handleActions(action) {
        switch (action.type) {
          case 'CREATE_TODO': {
            this.createTodo(action.text);
            break;
          }
          case 'DELETE_TODO': {
            this.deleteTodo(action.id);
            break;
          }
        }
      }
    }
      
    const todoStore = new TodoStore();
    dispatcher.register(todoStore.handleActions.bind(todoStore));
    export default todoStore;
    

    第 6 步(根组件): index.js 组件是应用程序的根组件。只有根组件应该知道 redux。需要注意的重要部分是连接函数,它用于将我们的根组件 App 连接到商店。此函数将选择函数作为参数。 select函数从 store 中获取状态并返回我们可以在组件中使用的 props (visibleTodos)。

    Todolist.js

    import React from 'react';
    import Todo from '../components/Todo';
    import TodoStore from '../stores/TodoStore.js';
    import * as TodoActions from '../actions/TodoActions';
      
    export default class Todolist extends React.Component {
      constructor() {
        super();
      
        this.state = {
          todos: TodoStore.getAll(),
        };
        this.inputContent = '';
      }
      
      // We start listening to the store changes
      componentWillMount() {
       TodoStore.on("change", () => {
         this.setState({
           todos: TodoStore.getAll(),
         });
       });
      }
      
      render() {
          
        const TodoComp = this.state.todos.map(todo => {
          return ;
        });
      
        return (
          
            

    GFG Todo list

            this.inputContent = evt.target.value} />                  
      {TodoComp}
          
        );   } }

    第 7 步:现在我们将删除任务组件。

    Todo.js

    import React from "react";
    import * as TodoActions from '../actions/TodoActions';
      
    export default class Todo extends React.Component{
        
      render(){
        return(
          
  •           {this.props.text}                
  •               );    } }

    第 8 步:其他组件

    index.js

    import React from 'react';
    import { render } from 'react-dom';
    import Todolist from './pages/Todolist';
      
    const app = document.getElementById('root');
      
    // render(React.createElement(Layout), app);
    render(, app);
    

    运行应用程序的步骤:打开终端并键入以下命令。

    npm start

    输出:创建按钮会将任务添加到待办事项列表中,类似地删除按钮将任务从待办事项列表中删除

    Redux: Redux 是 JavaScript 应用程序的可预测状态容器。 Dan Abramov 和 Andrew Clark 于 2015 年开发了 Redux。Redux 本身的库可以与任何 UI 层或框架一起使用,包括 React、Angular、Ember 和 vanilla JS。 Redux 可以与 React 一起使用。两者都是相互独立的。 Redux 是 JavaScript 应用程序中使用的状态管理库。它只是管理应用程序的状态,或者换句话说,它用于管理应用程序的数据。它与 React 之类的库一起使用。

    示例:现在我们将看到一个使用 react-redux 的简单示例计数器。在此示例中,我们存储了按钮单击的状态,并使用该状态来单击更多按钮,例如,我们创建四个按钮递增 (+)、递减 (-)、奇数递增、异步递增。

    • 递增 (+)、递减 (-):这两个按钮将点击递增 +1 和 -1
    • 如果为奇数则增加:此按钮仅在前两个按钮 (+) 和 (-) 的点击为奇数时增加点击,即如果点击为 7,则只有此按钮将增加 +1,现在点击将为 8,否则不会增加如果之前的按钮 (+) 和 (-) 点击为 6,因为 6 是偶数,如果奇数按钮仅在之前的状态为奇数点击时点击,则增加
    • 递增异步:此按钮将在暂停或等待 1000 英里后递增点击。秒。

    下面是分步实现:

    第 1 步:使用以下命令创建一个新的 react 应用程序。

    npx create-react-app counterproject
    npm install redux react-redux --save

    第 2 步:创建所有必需的文件和文件夹。

    项目结构:它将如下所示。

    文件和文件夹

    第 3 步: Counter.js 是一个展示组件,它关注事物的外观,例如标记、样式。它专门通过 props 接收数据并调用回调。它不知道数据来自哪里或如何更改它。它只呈现给他们的东西。这是计数器在 UI 中呈现所有内容的根组件。

    计数器.js

    import React, { Component } from 'react'
    import PropTypes from 'prop-types'
      
    class Counter extends Component {
      constructor(props) {
        super(props);
        this.incrementAsync = this.incrementAsync.bind(this);
        this.incrementIfOdd = this.incrementIfOdd.bind(this);
      }
      
      incrementIfOdd() {
        if (this.props.value % 2 !== 0) {
          this.props.onIncrement()
        }
      }
      
      incrementAsync() {
        setTimeout(this.props.onIncrement, 1000)
      }
      
      render() {
        const { value, onIncrement, onDecrement } = this.props
        return (
            
          

            

    GeeksForGeeks Counter Example

            Clicked: {value} times         {' '}                  {' '}                  {' '}                  {' '}                

        )   } }    Counter.propTypes = {   value: PropTypes.number.isRequired,   onIncrement: PropTypes.func.isRequired,   onDecrement: PropTypes.func.isRequired }    export default Counter

    第 4 步: Counter.spec.js 包含单击组件的特定按钮时要更改的内容。

    Counter.spec.js

    import React from 'react'
    import { shallow } from 'enzyme'
    import Counter from './Counter'
      
    function setup(value = 0) {
      const actions = {
        onIncrement: jest.fn(),
        onDecrement: jest.fn()
      }
      const component = shallow(
        
      )
      
      return {
        component: component,
        actions: actions,
        buttons: component.find('button'),
        p: component.find('p')
      }
    }
      
    describe('Counter component', () => {
      it('should display count', () => {
        const { p } = setup()
        expect(p.text()).toMatch(/^Clicked: 0 times/)
      })
      
      it('first button should call onIncrement', () => {
        const { buttons, actions } = setup()
        buttons.at(0).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('second button should call onDecrement', () => {
        const { buttons, actions } = setup()
        buttons.at(1).simulate('click')
        expect(actions.onDecrement).toBeCalled()
      })
      
      it('third button should not call onIncrement if the counter is even', () => {
        const { buttons, actions } = setup(42)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).not.toBeCalled()
      })
      
      it('third button should call onIncrement if the counter is odd', () => {
        const { buttons, actions } = setup(43)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('third button should call onIncrement if the counter is
        odd and negative', () => {
        const { buttons, actions } = setup(-43)
        buttons.at(2).simulate('click')
        expect(actions.onIncrement).toBeCalled()
      })
      
      it('fourth button should call onIncrement in a second', (done) => {
        const { buttons, actions } = setup()
        buttons.at(3).simulate('click')
        setTimeout(() => {
          expect(actions.onIncrement).toBeCalled()
          done()
        }, 1000)
      })
    })
    

    第 4 步:减速器

    这是一个 reducer,一个接受当前状态值和描述“发生了什么”的动作对象的函数,并返回一个新的状态值。 reducer 的函数签名是:(state, action) => newState。这意味着 reducer函数有两个参数stateaction 。 Redux 状态应该只包含普通的 JS 对象、数组和原语。根状态值通常是一个对象。重要的是,您不应该改变状态对象,而是在状态发生变化时返回一个新对象。你可以在 reducer 中使用任何你想要的条件逻辑。在此示例中,我们使用 switch 语句,但这不是必需的。

    index.js

    export default (state = 0, action) => {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1
        case 'DECREMENT':
          return state - 1
        default:
          return state
      }
    }
    

    索引.spec.js

    import counter from './index'
      
    describe('reducers', () => {
      describe('counter', () => {
        it('should provide the initial state', () => {
          expect(counter(undefined, {})).toBe(0)
        })
      
        it('should handle INCREMENT action', () => {
          expect(counter(1, { type: 'INCREMENT' })).toBe(2)
        })
      
        it('should handle DECREMENT action', () => {
          expect(counter(1, { type: 'DECREMENT' })).toBe(0)
        })
      
        it('should ignore unknown actions', () => {
          expect(counter(1, { type: 'unknown' })).toBe(1)
        })
      })
    })
    

    第 5 步:存储

    所有容器组件都需要访问 Redux Store 才能订阅它。为此,我们需要将它(存储)作为道具传递给每个容器组件。然而,它变得乏味。因此,我们建议使用一个特殊的 React Redux 组件,它可以让所有容器组件都可以使用 store,而无需显式传递它。它在渲染根组件时使用一次。

    index.js

    import React from 'react'
    import ReactDOM from 'react-dom'
    import { createStore } from 'redux'
    import Counter from './components/Counter'
    import counter from './reducers'
      
    const store = createStore(counter)
    const rootEl = document.getElementById('root')
      
    const render = () => ReactDOM.render(
       store.dispatch({ type: 'INCREMENT' })}
        onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
      />,
      rootEl
    )
      
    render()
    store.subscribe(render)
    

    运行应用程序的步骤:打开终端并键入以下命令。

    npm start

    输出:

    Redux 和 Flux 的区别:

    Sr.no Redux Flux
    1.It was developed by Dan Abramov & Andrew Clark.It was developed by Facebook.
    2. It is an Open-source JavaScript library used for creating the UI.It is Application architecture designed to build client-side web apps.
    3.

    Redux has mainly two components  

    • Action Creator
    • Store

    Flux has main four components :

    • Action
    • Dispatcher
    • Store
    • View
    4.Redux does not have any dispatcher.Flux has a single dispatcher.
    5. Redux has only a single store in the application.Flux has multiple stores in one application.
    6. In Redux, Data logics are in the reducers. In flux, Data logic is in store.
    7. In Redux store’s state cannot be mutableIn flux store’s state can be mutable.
    8.