Как только мы закончили [Глава 1: Создание приложения для реагирования и настройка Firebase] Список задач в реальном времени с Firebase, React.JS, Redux. Эта глава посвящена чтению и записи данных в firebase.

Глава 2
2.1 Структура данных.
2.2 Знакомство с Redux.
2.3 Чтение и запись в firebase.
2.4 Создание представления.

Давайте начнем…

2.1 Структура данных

Структура данных в данном случае означает, как данные выглядят в нашей базе данных. Что будем хранить. Мы делаем приложение todo, верно? поэтому у нас должны быть section и todo item. Раздел может содержать несколько задач. (У нас может быть несколько разделов).

Итак, мы собираемся хранить данные таким образом.

{
  id: 1,
  name: "Daily",
  todos: [{
    id: 1,
    name: "Feed the birds",
    timestamp: 1288389400
  },{
    id: 2,
    name: "Running",
    timestamp: 1288989400
  }],
  timestamp: 1277385300
}

** Название раздела: Ежедневно
** Ежедневно содержит 2 задачи («Покормить птиц »И« Бег »)

2.2 Настройка redux

Я не собираюсь углубляться в детали redux, но вы, ребята, можете прочитать больше из Redux, и мы собираемся использовать redux-thunk в качестве промежуточного программного обеспечения.

Сначала установите зависимости

$ npm install --save react-redux redux redux-thunk

Создайте configureStore.js для настройки хранилища redux.
Мы используем thunk в качестве промежуточного программного обеспечения.

src/components/configureStore.js
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import reducers from 'reducers'
export default () => {
  let middlewares = [thunk]
  let store = createStore(reducers, applyMiddleware(...middlewares))
  return store
}

изменить код в src / components / root.js

src/components/root.js
// ...
import {Provider} from 'react-redux'
import configureStore from './configureStore'
export default class Root extends Component {
  constructor(props) {
    super(props)
    firebaseInit()
    this.store = configureStore()
  }
  render() {
    return (
      <Provider store={this.store}>
        <Routes history={browserHistory}/>
      </Provider>
    )
  }
}

Как видите, configureStore.js требует reducers, поэтому позвольте создать todo.js в src / reducers

src/reducers/todo.js
import _ from 'lodash'
let initialState = {
  sections: []
}
export default (state = initialState, action) => {
  let newState = _.merge({}, state)
  switch(action.type) {
    default:
      return state
  }
}

Я использовал _.merge из lodash, чтобы создать клонированный объект состояния с новой ссылкой.

Во-вторых, позвольте объединить редукторы в один редуктор (в этом случае у нас есть только один редуктор, но мы можем объединить его, используя combReducers из redux )

Создайте index.js в src / reducers

src/reducers/index.js
import todo from './todo'
import {combineReducers} from 'redux'
export default combineReducers({todo})

Теперь ваша папка src должна выглядеть так. И все должны работать нормально. В интерфейсе ничего не изменилось.

src/
 |- components
   |- App/
   |- configureStore.js
   |- root.js
   |- routes.js
 |- javascripts
   |- firebase.js
 |- reducers
   |- index.js
   |- todo.js

2.3 Чтение и запись в firebase

Вернуться к коду…

Создайте модели для раздела и TodoItem. Создайте section.js и todo.js в src / javascripts / models

src/javascripts/models/section.js
export default (id, name, timestamp) => ({
  id: id,
  name: name,
  todos: [],
  timestamp: timestamp
})
src/javascripts/models/todo.js
export default (id, name, timestamp) => {
  return {
    id: id,
    name: name,
    timestamp: timestamp
  }
}

Затем давайте создадим функции для чтения и записи данных.

src/javascripts/firebase.js
// ...
import sectionModel from './models/section'
import todoModel from './models/todo'
// export const init = () ...
// retrieve from firebase
// return promise object
export const getSectionsDB = () => {
  return database.ref('/').once('value')
}
// get specified section
export const getTodoDB = (sectionId) => {
  return database.ref(`/${sectionId}`).once('value')
}
// add new section
export const addSection = (name) => {
  let key = database.ref('/').push().key
  let model = sectionModel(key, name, firebase.database.ServerValue.TIMESTAMP)
  return database.ref('/'+ key).set(model)
}
// add new todo item into specified section
export const addTodoItem = (id, name) => {
  return new Promise((resolve, reject) => {
    database.ref(`/${id}`).once('value').then((todo) => {
      let todos = todo.val().todos || []
      let key = database.ref(`/${id}`).push().key
      todos.push(todoModel(key, name, firebase.database.ServerValue.TIMESTAMP))
      database.ref(`/${id}/todos`).set(todos)
        .then( res => {resolve(res)})
        .catch( error => {reject(error)})
    })
  })
}

Все функции, которые мы только что добавили, возвращают объект Promise, который позволяет нам сохранять состояния приложения. Мы можем отображать панель загрузчика при загрузке данных и скрывать, когда данные загружаются и не удаляются.

В getTodoDB () я использовал `/ $ {sectionId}`, потому что разделы не сохраняются как массив в firebase, но он хранится как ключ (sectionId) и значение (объект раздела), как это

Если вам нужен ежедневный раздел, вы должны получить его с / -K_ZfUA4MxQG7AFswPad

И откуда взялись эти id? Это происходит из database.ref ('/'). Push (). Key, который предоставляется firebase в addSection ().

firebase.database.ServerValue.TIMESTAMP

- это метка времени сервера из базы данных firebase, поэтому мы используем ее для создания метки времени для наших данных.

Теперь ваша папка src должна выглядеть так. И ничего не изменилось в UI.

src/
 |- components
   |- App/
   |- comfigureStore.js
   |- root.js
   |- routes.js
 |- javascripts
   |- models
     |- section.js
     |- todo.js
   |- firebase.js
 |- reducers
   |- index.js
   |- todo.js

2.4 Создание представления

Поскольку у нас есть код для чтения и записи данных в firebase, но у нас нет представления для выполнения этого кода, поэтому давайте создадим их.

Замените index.js в src / components / App / index.js этим кодом.

src/components/App/index.js
import React, { Component } from 'react';
import SectionList from './section-list'
import {connect} from 'react-redux'
import {loadSections, createSection} from 'actions/todo'
class App extends Component {
  componentDidMount() {
    this.props.loadSections()
  }
onSubmit = (e) => {
    e.preventDefault()
    let ref = this.refs['section-name']
    let sectionName = ref.value
    this.props.createSection(sectionName)
    ref.value = ''
  }
render() {
    return (
      <div>
        <SectionList sections={this.props.sections}/>
        <form onSubmit={this.onSubmit}>
          <input ref="section-name"/>
          <button>Add new section</button>
        </form>
      </div>
    );
  }
}
const mapStateToProps = (state) => {
  return {
    sections: state.todo.sections
  }
}
export default connect(mapStateToProps, {loadSections, addSection})(App)

Создайте компоненты SectionList как section-list.js в src / components / App / для отображения разделов в виде списка

src/components/App/section-list.js
export default (props) => {
  return (
    <ul>
      {
        _.map(props.sections, (section) => <li>{section.name}</li>)
      }
    </ul>
  )
}

В index.js вы видите, что у нас есть ‹ul› и ‹li› для отображения разделов и ‹form› для создания нового раздела.

Мы импортируем loadSections, createSection из actions / todo и loadSections вызывается в componentDidMount (). Это означает, что новые разделы будут загружаться каждый раз при отображении этой страницы.

Итак, позвольте создать todo.js в src / actions

src/actions/todo.js
import { getSectionsDB, addSection } from 'javascripts/firebase'
import actionType from 'constants'
export const loadSections = () => {
 return dispatch => {
  dispatch({
   type: actionType.LOAD_SECTIONS_REQUEST
  })
  getSectionsDB()
   .then(sections => {
    dispatch({
     type: actionType.LOAD_SECTIONS_SUCCESS,
     payload: sections.val()
    })
   })
   .catch(error => {
    dispatch({
     type: actionType.LOAD_SECTIONS_FAILED,
     payload: error
    })
   })
 }
}
export const createSection = (name) => {
 return dispatch => {
  dispatch({
   type: actionType.ADD_SECTION_REQUEST
  })
  addSection(name)
   .then(res => {
    loadSections()(dispatch) //refresh the data to keep up-to-date
    dispatch({
     type: actionType.ADD_SECTION_SUCCESS
    })
   })
   .catch(error => {
    dispatch({
     type: actionType.ADD_SECTION_FAILED,
     payload: error
    })
   })
 }
}

Но в action / todo.js требуется константа (actionType), которая указывает тип действия (REQUEST, SUCCESS или НЕ УДАЛОСЬ).

Создайте index.js в src / constants.

export default {
  LOAD_SECTIONS_REQUEST : 'LOAD_SECTIONS_REQUEST',
  LOAD_SECTIONS_SUCCESS : 'LOAD_SECTIONS_SUCCESS',
  LOAD_SECTIONS_FAILED : 'LOAD_SECTIONS_FAILED',
  ADD_SECTION_SUCCESS : 'ADD_SECTION_SUCCESS',
  ADD_SECTION_REQUEST : 'ADD_SECTION_REQUEST',
  ADD_SECTION_FAILED : 'ADD_SECTION_FAILED'
}

** Вы также можете использовать MirrorCreator

Теперь ваша папка src должна выглядеть так

src/
 |- actions
   |- todo.js
 |- components
   |- App/
     |- ...
     |- section-list.js
   |- comfigureStore.js
   |- root.js
   |- routes.js
 |- constants
   |- index.js
 |- javascripts
   |- models
     |- section.js
     |- todo.js
   |- firebase.js
 |- reducers
   |- index.js
   |- todo.js

Позвольте мне объяснить, что мы только что сделали, потому что я знаю, что вы не знаете, что только что сделали. Вы только копируете и вставляете код.

App / index.js, который является представлением (содержащим HTML-код). Он получит разделы из хранилища (mapStateToProps) и изменит хранилище из действия (action / todo.js). Когда мы нажимаем кнопку Добавить новый раздел, вызывается createSection () в action / todo.js и действие выполнит код firebase для добавления ваших данных в firebase. Кроме того, мы loadSection () каждый раз после рендеринга представления. Это также выполняет код firebase и передает результат от firebase редуктору.

actions / todo.js - файл, в котором хранятся все действия, которые могут быть выполнены представлением. И каждый результат действий будет передан редуктору ( reducers / todo.js ) в виде полезная нагрузка и тип .

redurs / todo.j s - редуктор, содержащий логику приложения. Редуктор решит, какие данные какому состоянию приложения принадлежат, и изменит состояние в хранилище. Обычно он обрабатывает необработанные данные из действия.

store содержит состояние приложения. В данном случае это {todo: [], isLoading: false}

Простой поток редукции

View = ›(вызов) Action =› (передать результат) Reducer = ›(изменить) Store =› (обработано) View = ›…

Хорошо, теперь я надеюсь, вы знаете, что мы делаем. Теперь ваш пользовательский интерфейс должен быть изменен на этот

Давайте попробуем создать новый раздел, введя имя раздела и нажав кнопку.

Да да, проверьте свою консоль firebase. В вашу базу данных добавлен новый элемент.

И снова обновите страницу своего веб-приложения. Он должен загружать и отображать разделы, верно? Почему не отображается ни один раздел?

На самом деле он уже загрузил разделы из firebase, но редуктор не знает, что ему делать с данными.

Из loadSections () в action / todo.js данные будут передаваться с помощью LOAD_SECTIONS_SUCCESS в reducer, затем позвольте редуктору обрабатывать данные.

Измените код в src / reducer / todo.js

src/reducer/todo.js
//...
import actionType from 'constants'
//...
export default (state = initialState, action) => {
  let newState = _.merge({}, state)
  switch(action.type) {
    case actionType.LOAD_SECTIONS_SUCCESS:
      newState.sections = action.payload
      return newState
    default:
      return state
  }
}

Мы добавили еще один случай, LOAD_SECTIONS_SUCCESS, и установили для newState.sections действие . полезная нагрузка, которая представляет собой разделы из firebase.

Теперь снова обновите свое веб-приложение.

Разделы должны отображаться.

Поиграйте с этим.

Следующая глава посвящена созданию элемента задачи и использованию response-redux-router.

Давайте продолжим на [Глава 3: Страница перенаправления с помощью response-router] Список задач в реальном времени с Firebase, React.JS, Redux