betacode

Простой пример с React и Redux на стороне клиента

  1. Цель примера
  2. Код приложения
  3. Объяснение кода приложения

1. Цель примера

В данной статье я покажу вам как создать маленькое приложение с React & Redux. При этом библиотека Redux помогает управлять состоянием приложения. Это приложение полностью работает на стороне Client (Клиентская сторона).
Ниже является демо изображение приложения:
Библиотеки использованные в данном приложении:
Libraries
<!--
   NOTE: Check other version at: https://cdnjs.com
   -->
<!-- React Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>

<!-- Redux & React-Redux Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>

<!-- Babel Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>
Архитектура Redux:
Вы можете посмотреть мою статью "Введение в Redux" по ссылке ниже, чтобы больше понять про Redux и его архитектуру, так же изучить про отличие между Flux и Redux.

2. Код приложения

Создать 2 файла react-redux-example.html & react-redux-example.jsx:
react-redux-example.html
<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8">
      <title>React/Redux for beginners</title>
      <!--
         NOTE: Check other version at: https://cdnjs.com
      -->
      <!-- React Libraries -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>

      <!-- Redux & React-Redux Libraries -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>

      <!-- Babel Library -->
      <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.26.0/babel.min.js"></script>

      <style>
        table {
          border-collapse: collapse;
        }
        table td, th {
          padding: 5px;
        }
      </style>
   </head>
   <body>

     <p>Install
       <a target="_blank"
         href="https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd/related?hl=en">
         Redux DevTools
       </a>
       Chrome Extension to see under the hood
     </p>

      <div id="app">
         Loading...
      </div>

      <script type="text/babel" src="react-redux-example.jsx"></script>

   </body>
</html>
react-redux-example.jsx
// REACT COMPONENT
// Generally you would spilt this up into logical components
// and pass props around correctly etc but we are keeping it as simply as possible
class ProductsPane extends React.Component {
  constructor (props, context) {
    super(props, context);
  }

  // EVENT HANDLERS
  // They are responsible for calling `dispatch` which will send events to redux
  addProduct = () => {
    var action = {
        type: 'ADD_PRODUCT'
    };
    this.props.dispatch(action);
  }

  removeProduct  = (productId) =>  {
    var action = {
      type: 'REMOVE_PRODUCT',
      productId: productId
    };
    this.props.dispatch(action);
  }

  editProduct = (productId, event) =>   {
    var newName =  event.target.value;
    var action = {
      type: 'EDIT_PRODUCT',
      data: {
        productId: productId,
        productName: newName
      }
    };
    this.props.dispatch(action);
  }


  render () {
    const products = this.props.products;

    // Example product: { productId : 4 , productName :'Profit' }
    var trList = products.map( (product, index) => {
      return (<tr key={product.productId}>
        <td>{product.productId}</td>
        <td><input type="text" onChange={this.editProduct.bind(null, product.productId)} value={product.productName} /></td>
        <td>
          <button onClick={this.removeProduct.bind(null, product.productId)}>
             Remove
          </button>
        </td>
      </tr>);
    });


    return (<div>
      <table border="1">
        <thead>
           <th>ID</th>
           <th>Product Name</th>
           <th>Remove</th>
        </thead>
        <tbody>
          {trList}
        </tbody>
      </table>
      <br/>
      <button onClick={this.addProduct}>
          Create
      </button>
    </div>);
  }
}

var nextProductId = 10;

// MAP STATE TO PROPS
// Probably the most important method of the demo which handles the React/Redux integration.
// When state changes, this method is called, which then you can use to customly
// map the state into props that your React component can use
// MAP: state.productList <==> props.products
const mapStateToProps = (state) => {
  return {
    products: state.productList
  }
}

// Example product: { productId : 4 , productName :'Profit' }
const getIndexByProductId = (products, productId)  => {
    for(var i = 0; i < products.length; i++) {
       var product = products[i];
       if(product.productId === productId) {
         return i;
       }
    }
    return -1;
};



// REDUCERS
// Reducers listen for actions that are dispatched and react depending on your logic
// All state in Redux is immutable(never changes) so we always have to return a new
// state object.
// We are going to copy the current state and return a new one based off the action creators above
const appReducer = (state = {productList: []}, action) => {

  // Clone Array.
  let products = state.productList.slice();
  // This is quite a common way of deciding which event to process
  // Note: ALL events will be coming through this reducer
  console.log('Actions', action); // Open your console to see what actions look like

  // Even better, install Redux DevTools and your mind will be blown
  switch (action.type) {
    case 'ADD_PRODUCT':
      nextProductId++;
      var product = {productId : nextProductId, productName: "" };
      products.push(product);
      break;
    case 'REMOVE_PRODUCT':
      var idx = getIndexByProductId(products, action.productId);
      if(idx != -1)  {
        products.splice(idx, 1); // Removes element at `idx`
      }
      break;
    case 'EDIT_PRODUCT':
      var idx = getIndexByProductId(products, action.data.productId);
      if(idx != -1)  {
          products[idx].productName = action.data.productName;
      }
      break;
  }
  // As above, we have to return a new state object each time (Redux store is immutable)
  // It makes sure we know our data can only be modified in one visible way
  // Also lets us time travel through our application state!
  const newState = {
    productList: products
  }
  console.log('Current State', newState);
  return newState;
}

// REDUX STORE
// Create store initializes our Redux store and only has to be called once
// The first argument is our `appReducer` defined above, it is almost like a listener
// The second is just our initial state which is just a Javascript object
// The third is usually where enhancers/middleware goes
// In this example it just loads Redux DevTools so everyone can play around
let store = Redux.createStore(appReducer, {
  productList: [
    { productId : 1 , productName :'React' },
    { productId : 2 , productName :'Redux' },
    { productId : 3 , productName :'Profit' }
  ]
}, window.devToolsExtension ? window.devToolsExtension() : undefined);

// We want to use Redux connect to attach our mapStateToProps to our ProductsPane (React Component)
const MyApp = ReactRedux.connect (
  mapStateToProps
)(ProductsPane);

// Render
ReactDOM.render(
  <ReactRedux.Provider store={store}>
    <MyApp />
  </ReactRedux.Provider>,
  document.getElementById('app')
);


if (window.devToolsExtension) {
  window.devToolsExtension.open();
}
Запуск файла react-redux-example.html на HTTP Server:

3. Объяснение кода приложения

OK, в данном приложении мы используем React и Redux. При этом Redux это библиотека Javascript которая помогает управлять состоянием приложения. Ниже является изображение архитектуры Redux.
Когда пользователь взаимодействует на интерфейсе приложения (Например нажимает на кнопку мыши), вызывается метод Component для выполнения. В данном методе создается объект action, это чистый объект, содержащий информацию связанную с событиями и состояниями, которые нужно обновить. В конце отправить данный объект к STORE вызывая this.props.dispatch(action).
REDUCER: (Редуктор) Это функция Javascript, и вам нужно написать код для данной функции чтобы вернуть новое состояние для приложения. Имеет 2 параметра:
  1. Параметр 1: Начальное состояние приложения.
  2. Параметр 2: Объект action отправлен из предыдущего шага.
В Redux, STORE является единственным, он хранит состояние приложения, и содержит Reducer и Middleware. Вы можете создать STORE через метод Redux.createStore(..).
// createStore method:
Redux.createStore(reducer, [preloadedState], [enhancer])

// @see API docs: https://redux.js.org/api/createstore
После обработки action на STORE, новое состояние возвращается к VIEW PROVIDER.
Новое состояние обновляется для Component: