Подробное объяснение использования фреймворка dva и демонстрационный учебник.

GitHub JavaScript React.js Redux

Некоторое время назад мы также изучили и объяснили основы использования фреймворка Redux, но многие студенты в группе по обмену отзывались о том, что фреймворк redux сложен для понимания.После прочтения я все еще был в замешательстве и не знал, как Чтобы начать, многие студенты обратились к выбору использования структуры dva. Фактически, фреймворк dva представляет собой комбинацию фреймворка redux и фреймворка redux-saga.Он переупаковывает несколько часто используемых фреймворков обработки данных, что обеспечивает удобство для пользователей с точки зрения использования.Давайте кратко представим базовый API и базовое использование фреймворка dva.

Демонстрация запущенных рендеров

Подобно объяснению фреймворка Redux, автор по-прежнему предоставляет два классических демонстрационных примера, CounterApp и TodoList, чтобы помочь новичкам лучше понять и использовать

http://ovyjkveav.bkt.clouddn.com/17-12-22/25369015.jpg

http://ovyjkveav.bkt.clouddn.com/17-12-22/35513550.jpg

Демонстрационный адрес

  • CounterApp

GitHub.com/LightIntensity-…

  • TodoList

GitHub.com/LightIntensity-…

Происхождение слова два

У D.Va есть мощный мех с двумя полностью автоматическими термоядерными пушками ближнего действия, двигателями, которые позволяют меху перепрыгивать через врагов или препятствия, и защитной матрицей, которая может защищать от дальних атак спереди. — из Овервотч.

официальный адрес два

GitHub.com/But VA Technologies/DV Ah/No…

двухъядерный API

  • app = dva(opts)

Создайте приложение и верните экземпляр dva (Примечание: dva поддерживает несколько экземпляров)

optsСодержит следующую конфигурацию:

  1. history: История, назначенная для маршрутизации, по умолчанию — hashHistory.
  2. initialState: Укажите исходные данные, приоритет выше, чем состояние в модели, по умолчанию {}

Если настроеноhistoryДля browserHistory создание объекта dva можно записать следующим образом

import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
})

Кроме того, для простоты использования, все хуки также могут быть настроены в опциях, ниже перечислены все настраиваемые свойства:

const app = dva({
  history,
  initialState,
  onError,
  onAction,
  onStateChange,
  onReducer,
  onEffect,
  onHmr,
  extraReducers,
  extraEnhancers,
})
  • app.use(hooks)

Настройте хуки или зарегистрируйте плагины. (Плагин наконец-то возвращает хуки)

например, регистрацияdva-loadingПримеры плагинов:

import createLoading from 'dva-loading'
...
app.use(createLoading(opts))

hooksСодержит следующие элементы конфигурации:

1,onError((err, dispatch) => {})

Запускается, когда ошибка выполнения эффекта или подписка активно выдает ошибку завершения, которую можно использовать для управления глобальным состоянием ошибки.

Уведомление: Подписка не добавляет try...catch, поэтому если есть ошибка, то нужно активно кидать ошибку через второй параметр done

пример:

app.model({
  subscriptions: {
    setup({ dispatch }, done) {
      done(e)
    },
  },
})

Если мы используем компоненты antd, простейшая глобальная обработка ошибок обычно делает это:

import { message } from 'antd'
const app = dva({
  onError(e) {
    message.error(e.message, 3)
  },
})

2,onAction(fn | fn[])

Запускается при отправке действия, используется для регистрации промежуточного ПО Redux. Функция поддержки или формат массива функций

Например, мы хотим печатать логи через redux-logger:

import createLogger from 'redux-logger';
const app = dva({
  onAction: createLogger(opts),
})

3.onStateChange(fn)

stateЗапускается при изменении, может использоваться для синхронизации состояния с локальным хранилищем, на стороне сервера и т. д.

4.onReducer(fn)

Инкапсулируйте выполнение редюсера, например, реализацию повтора/отмены с помощью redux-undo:

import undoable from 'redux-undo';
const app = dva({
  onReducer: reducer => {
    return (state, action) => {
      const undoOpts = {};
      const newState = undoable(reducer, undoOpts)(state, action);
      // 由于 dva 同步了 routing 数据,所以需要把这部分还原
      return { ...newState, routing: newState.present.routing };
    },
  },
})

5.onEffect(fn)

Инкапсулирует выполнение эффекта. Напримерdva-loadingНа основании этого реализована автоматическая обработка состояния загрузки

6.onHmr(fn)

Горячая замена, в настоящее время используется дляbabel-plugin-dva-hmr

7.extraReducers

Укажите дополнительные редукторы, такие какredux-formНеобходимо указать дополнительные редукторы формы:

import { reducer as formReducer } from 'redux-form'
const app = dva({
  extraReducers: {
    form: formReducer,
  },
})
  • app.model(model)

Регистрация модели, эта операция является основной операцией в dva, которая будет подробно объяснена ниже.

  • app.unmodel(namespace)

Отмените регистрацию модели, очистите редукторы, эффекты и подписки. Если подписка не вернет функцию unlisten, использование app.unmodel выдаст предупреждение⚠️

  • app.router(({ history, app }) => RouterConfig)

Зарегистрируйте таблицу маршрутизации, этот шаг операции также очень важен в dva.

// 注册路由
app.router(require('./router'))
// 路由文件
import { Router, Route } from 'dva/router';
import IndexPage from './routes/IndexPage'
import TodoList from './routes/TodoList'

function RouterConfig({ history }) {
  return (
    <Router history={history}>
        <Route path="/" component={IndexPage} />
        <Route path='/todoList' components={TodoList}/>
    </Router>
  )
}
export default RouterConfig

Конечно, если мы хотим решить проблему динамической загрузки компонентов, наш файл роутинга можно записать и так:

import { Router, Switch, Route } from 'dva/router'
import dynamic from 'dva/dynamic'

function RouterConfig({ history, app }) {
  const IndexPage = dynamic({
    app,
    component: () => import('./routes/IndexPage'),
  })

  const Users = dynamic({
    app,
    models: () => [import('./models/users')],
    component: () => import('./routes/Users'),
  })

  return (
    <Router history={history}>
      <Switch>
        <Route exact path="/" component={IndexPage} />
        <Route exact path="/users" component={Users} />
      </Switch>
    </Router>
  )
}

export default RouterConfig

вdynamic(opts)Опция содержит три элемента конфигурации:

  • opts

    • app: экземпляр dva, необходимый для загрузки моделей
    • модели: функция, которая возвращает массив обещаний, обещание возвращает модель dva
    • Компонент: возвращает функцию обещания, обещание возвращает React Compot
  • app.start(selector?)

Запустите приложение, селектор необязателен, если параметр селектора отсутствует, оно вернет функцию, которая возвращает элемент JSX.

app.start('#root')

Так когда не добавлять селектор? Общие сценарии включают в себя тестирование, поддержку node, react-native и интернационализацию i18n.

Например, пример поддержки интернационализации через react-intl:

import { IntlProvider } from 'react-intl'
...
const App = app.start()
ReactDOM.render(<IntlProvider><App /></IntlProvider>, htmlElement)

Базовый слой в фреймворке dva: Модель

Ниже приведен простой общийmodelзапись файла

/** Created by guangqiang on 2017/12/17. */

import queryString from 'query-string'
import * as todoService from '../services/todo'

export default {
  namespace: 'todo',
  state: {
    list: []
  },
  reducers: {
    save(state, { payload: { list } }) {
      return { ...state, list }
    }
  },
  effects: {
    *addTodo({ payload: value }, { call, put, select }) {
      // 模拟网络请求
      const data = yield call(todoService.query, value)
      console.log(data)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      const tempObj = {}
      tempObj.title = value
      tempObj.id = list.length
      tempObj.finished = false
      list.push(tempObj)
      yield put({ type: 'save', payload: { list }})
    },
    *toggle({ payload: index }, { call, put, select }) {
      // 模拟网络请求
      const data = yield call(todoService.query, index)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      let obj = list[index]
      obj.finished = !obj.finished
      yield put({ type: 'save', payload: { list } })
    },
    *delete({ payload: index }, { call, put, select }) {
      const data = yield call(todoService.query, index)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      list.splice(index, 1)
      yield put({ type: 'save', payload: { list } })
    },
    *modify({ payload: { value, index } }, { call, put, select }) {
      const data = yield call(todoService.query, value)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      let obj = list[index]
      obj.title = value
      yield put({ type: 'save', payload: { list } })
    }
  },
  subscriptions: {
    setup({ dispatch, history }) {
      // 监听路由的变化,请求页面数据
      return history.listen(({ pathname, search }) => {
        const query = queryString.parse(search)
        let list = []
        if (pathname === 'todoList') {
          dispatch({ type: 'save', payload: {list} })
        }
      })
    }
  }
}

Объект модели содержит 5 важных свойств:

  • namespace

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

  • state

Начальное значение редуктора, приоритет ниже переданного в dva()opts.initialState

Например:

const app = dva({
  initialState: { count: 1 },
});
app.model({
  namespace: 'count',
  state: 0,
})

В это время вapp.start()После того, как state.count равен 1

  • reducers

Определите редюсер в формате ключ/значение для обработки синхронных операций, единственное место, где состояние может быть изменено, запускается действием

Формат(state, action) => newStateили[(state, action) => newState, enhancer]

namespace: 'todo',
  state: {
    list: []
  },
  // reducers 写法
  reducers: {
    save(state, { payload: { list } }) {
      return { ...state, list }
    }
  }
  • effects

Определите эффекты в формате ключ/значение. Используется для обработки асинхронных операций и бизнес-логики без прямого изменения состояния. Запускается действием, может запускать действие, может взаимодействовать с сервером, может получать глобальные данные о состоянии и т. д.

Уведомление:Идея дизайна модуля эффектов в фреймворке dva исходит отredux-sagaрамки, если учащиесяredux-sagaЕсли вы не знакомы с фреймворком, вы можете проверить авторское объяснение redux-saga:woo woo Краткое описание.com/afraid/7 wipe 18 oh 8 of…

Формат*(action, effects) => voidили[*(action, effects) => void, { type }]

Существует четыре типа типа:

1,takeEvery

2,takeLatest

3.throttle

4.watcher

// effects 写法
effects: {
    *addTodo({ payload: value }, { call, put, select }) {
      // 模拟网络请求
      const data = yield call(todoService.query, value)
      console.log(data)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      const tempObj = {}
      tempObj.title = value
      tempObj.id = list.length
      tempObj.finished = false
      list.push(tempObj)
      yield put({ type: 'save', payload: { list }})
    },
    *toggle({ payload: index }, { call, put, select }) {
      // 模拟网络请求
      const data = yield call(todoService.query, index)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      let obj = list[index]
      obj.finished = !obj.finished
      yield put({ type: 'save', payload: { list } })
    },
    *delete({ payload: index }, { call, put, select }) {
      const data = yield call(todoService.query, index)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      list.splice(index, 1)
      yield put({ type: 'save', payload: { list } })
    },
    *modify({ payload: { value, index } }, { call, put, select }) {
      const data = yield call(todoService.query, value)
      let tempList = yield select(state => state.todo.list)
      let list = []
      list = list.concat(tempList)
      let obj = list[index]
      obj.title = value
      yield put({ type: 'save', payload: { list } })
    }
  }
  • subscriptions

Определите подписку в формате ключа / значения, подписка - это подписка, используемая для подписки на источник данных, а затем отправлять соответствующие действия по мере необходимости

Выполняется в app.start(), источник данных может быть текущим временем, подключением к веб-сокету сервера, вводом с клавиатуры, изменениями геолокации, изменениями истории маршрутизации и т. д.

Формат({ dispatch, history }, done) => unlistenFunction

Уведомление: если вы хотите использовать app.unmodel(), подписка должна возвращать метод unlisten для отмены подписки на данные.

// subscriptions 写法
subscriptions: {
    setup({ dispatch, history }) {
      // 监听路由的变化,请求页面数据
      return history.listen(({ pathname, search }) => {
        const query = queryString.parse(search)
        let list = []
        if (pathname === 'todoList') {
          dispatch({ type: 'save', payload: {list} })
        }
      })
    }
  }

Разница между использованием фреймворка dva и прямым использованием redux

  • использовать избыточность

Actions.js Файл

export const REQUEST_TODO = 'REQUEST_TODO';
export const RESPONSE_TODO = 'RESPONSE_TODO';

const request = count => ({type: REQUEST_TODO, payload: {loading: true, count}});

const response = count => ({type: RESPONSE_TODO, payload: {loading: false, count}});

export const fetch = count => {
  return (dispatch) => {
    dispatch(request(count));

    return new Promise(resolve => {
      setTimeout(() => {
        resolve(count + 1);
      }, 1000)
    }).then(data => {
      dispatch(response(data))
    })
  }
}

файл редуктора.js

import { REQUEST_TODO, RESPONSE_TODO } from './actions';

export default (state = {
  loading: false,
  count: 0
}, action) => {
  switch (action.type) {
    case REQUEST_TODO:
      return {...state, ...action.payload};
    case RESPONSE_TODO:
      return {...state, ...action.payload};
    default:
      return state;
  }
}

файл app.js

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import * as actions from './actions';

const App = ({fetch, count, loading}) => {
  return (
    <div>
      {loading ? <div>loading...</div> : <div>{count}</div>}
      <button onClick={() => fetch(count)}>add</button>
    </div>
  )
}

function mapStateToProps(state) {
  return state;
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

файл index.js

import { render } from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk';

import reducer from './app/reducer';
import App from './app/app';

const store = createStore(reducer, applyMiddleware(thunkMiddleware));

render(
  <Provider store={store}>
    <App/>
  </Provider>
  ,
  document.getElementById('app')
)
  • использовать два

файл model.js

export default {
  namespace: 'demo',
  state: {
    loading: false,
    count: 0
  },
  reducers: {
    request(state, payload) {
      return {...state, ...payload};
    },
    response(state, payload) {
      return {...state, ...payload};
    }
  },
  effects: {
    *'fetch'(action, {put, call}) {
      yield put({type: 'request', loading: true});

      let count = yield call((count) => {
        return new Promise(resolve => {
          setTimeout(() => {
            resolve(count + 1);
          }, 1000);
        });
      }, action.count);

      yield put({
        type: 'response',
        loading: false,
        count
      });
    }
  }
}

файл app.js

import React from 'react'
import { connect } from 'dva';

const App = ({fetch, count, loading}) => {
  return (
    <div>
      {loading ? <div>loading...</div> : <div>{count}</div>}
      <button onClick={() => fetch(count)}>add</button>
    </div>
  )
}

function mapStateToProps(state) {
  return state.demo;
}

function mapDispatchToProps(dispatch) {
  return {
    fetch(count){
      dispatch({type: 'demo/fetch', count});
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

файл index.js

import dva from 'dva';
import model from './model';
import App from './app';

const app = dva();

app.use({});

app.model(model);

app.router(() => <App />);

app.start();

Мы находим структуру кода асинхронного счетчика двумя указанными выше способами:

  1. Использование редукса должно быть разделеноactionмодули иreducerмодуль

  2. два будетactionа такжеreducerупаковано вmodel, асинхронный процесс обрабатывает Генератор

Суммировать

В этой статье в основном объясняется разработка общих API-интерфейсов и некоторые навыки использования в среде dva.Если вы хотите просмотреть более подробные API-интерфейсы, обратитесь к официальной документации dva:github.com/dvajs/dva

Если студенты все еще не знают, как использовать фреймворк dva после прочтения учебника, рекомендуется запустить демонстрационный пример, предоставленный автором, чтобы совместить обучение

Предложение автора: Учащиеся могут поделиться тем, что автор объяснил ранее.редукционная структураСравните с фреймворком два, чтобы научиться понимать, чтобы различия и связи между ними были яснее.

время благосостояния

  • Адрес OneM авторского проекта с открытым исходным кодом React Native (разработан путем построения фреймворка в соответствии со стандартами корпоративной разработки):GitHub.com/LightIntensity-…: добро пожаловать друзьяstar
  • Краткая домашняя страница автора: содержит более 60 технических статей, связанных с разработкой RN.у-у-у. Краткое описание.com/U/023338566…Добро пожаловать друзья:уделять больше внимания,как более
  • Автор группы технического обмена React Native QQ:620792950Приглашаем друзей присоединиться к группе для обмена и обучения
  • дружеское напоминание:При разработке возникли технические проблемы, связанные с RN, и друзья могут присоединиться к группе обмена (620792950), задавайте вопросы в группе, общайтесь и учитесь друг у друга. Группа обмена также регулярно обновляет последние учебные материалы RN для всех, спасибо за вашу поддержку!