Как только мы закончили [Глава 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