一切前端概念,都是纸老虎(附送书的抽奖结果)

View 肯定是要展示数据的,所谓的数据,就是 Store,Store 很容易明白,就是存数据的地方。当然我们可以把 Store 都放到一起,也可以分开来放,所以就有一堆的 Store。但是这些 View 都有一个特点,就是 Store 变了得跟着变。

View 怎么跟着变呢?一般 Store 一旦发生改变,都会往外面发送一个事件,比如 change,通知所有的订阅者。View 通过订阅也好,监听也好,不同的框架有不同的技术,反正 Store 变了,View 就会变。

View 不是光用来看的,一般都会有用户操作,用户点个按钮,改个表单啥的,就需要修改 Store。Flux 要求,View 要想修改 Store,必须经过一套流程,有点像我们刚才 Store 模式里面说的那样。视图先要告诉 Dispatcher,让 Dispatcher dispatch 一个 action,Dispatcher 就像是个中转站,收到 View 发出的 action,然后转发给 Store。比如新建一个用户,View 会发出一个叫 addUser 的 action 通过 Dispatcher 来转发,Dispatcher 会把 addUser 这个 action 发给所有的 store,store 就会触发 addUser 这个 action,来更新数据。数据一更新,那么 View 也就跟着更新了。

这个过程有几个需要注意的点:Dispatcher 的作用是接收所有的 Action,然后发给所有的 Store。这里的 Action 可能是 View 触发的,也有可能是其他地方触发的,比如测试用例。转发的话也不是转发给某个 Store,而是所有 Store。Store 的改变只能通过 Action,不能通过其他方式。也就是说 Store 不应该有公开的 Setter,所有 Setter 都应该是私有的,只能有公开的 Getter。具体 Action 的处理逻辑一般放在 Store 里。

听听描述看看图,可以发现,Flux的最大特点就是数据都是单向流动的。

Redux

Flux 有一些缺点(特点),比如一个应用可以拥有多个 Store,多个Store之间可能有依赖关系;Store 封装了数据还有处理数据的逻辑。

所以大家在使用的时候,一般会用 Redux,他和 Flux 思想比较类似,也有差别。

Store

Redux 里面只有一个 Store,整个应用的数据都在这个大 Store 里面。Store 的 State 不能直接修改,每次只能返回一个新的 State。Redux 整了一个 createStore 函数来生成 Store。

import { createStore } from ‘redux’;const store = createStore(fn);

Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。这样不管 View 是用什么实现的,只要把 View 的更新函数 subscribe 一下,就可以实现 State 变化之后,View 自动渲染了。比如在 React 里,把组件的render方法或setState方法订阅进去就行。

Action

和 Flux 一样,Redux 里面也有 Action,Action 就是 View 发出的通知,告诉 Store State 要改变。Action 必须有一个 type 属性,代表 Action 的名称,其他可以设置一堆属性,作为参数供 State 变更时参考。

const action = {

type: ‘ADD_TODO’,

payload: ‘Learn Redux’};

Redux 可以用 Action Creator 批量来生成一些 Action。

Reducer

Redux 没有 Dispatcher 的概念,Store 里面已经集成了 dispatch 方法。store.dispatch()是 View 发出 Action 的唯一方法。

import { createStore } from ‘redux’;const store = createStore(fn);

store.dispatch({

type: ‘ADD_TODO’,

payload: ‘Learn Redux’});

Redux 用一个叫做 Reducer 的纯函数来处理事件。Store 收到 Action 以后,必须给出一个新的 State(就是刚才说的Store 的 State 不能直接修改,每次只能返回一个新的 State),这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

什么是纯函数呢,就是说没有任何的副作用,比如这样一个函数:

function getAge(user) {

user.age = user.age + 1;

return user.age;}

这个函数就有副作用,每一次相同的输入,都可能导致不同的输出,而且还会影响输入 user 的值,再比如:

let b = 10;function compare(a) {

return a >= b;}

这个函数也有副作用,就是依赖外部的环境,b 在别处被改变了,返回值对于相同的 a 就有可能不一样。

而 Reducer 是一个纯函数,对于相同的输入,永远都只会有相同的输出,不会影响外部的变量,也不会被外部变量影响,不得改写参数。它的作用大概就是这样,根据应用的状态和当前的 action 推导出新的 state:

(previousState, action) => newState

类比 Flux,Flux 有些像:

(state, action) => state

为什么叫做 Reducer 呢?reduce 是一个函数式编程的概念,经常和 map 放在一起说,简单来说,map 就是映射,reduce 就是归纳。映射就是把一个列表按照一定规则映射成另一个列表,而 reduce 是把一个列表通过一定规则进行合并,也可以理解为对初始值进行一系列的操作,返回一个新的值。

比如 Array 就有一个方法叫 reduce,Array.prototype.reduce(reducer, ?initialValue),把 Array 整吧整吧弄成一个 newValue。

const array1 = [1, 2, 3, 4];const reducer = (accumulator, currentValue) => accumulator + currentValue;// 1 + 2 + 3 + 4

console.log(array1.reduce(reducer));// expected output: 10// 5 + 1 + 2 + 3 + 4

console.log(array1.reduce(reducer, 5));// expected output: 15

看起来和 Redux 的 Reducer 是不是好像好像,Redux 的 Reducer 就是 reduce 一个列表(action的列表)和一个 initialValue(初始的 State)到一个新的 value(新的 State)。

把上面的概念连起来,举个例子:

下面的代码声明了 reducer:

const defaultState = 0;const reducer = (state = defaultState, action) => {

switch (action.type) {

case ‘ADD’:

return state + action.payload;

default:

return state;

}};

createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。

import { createStore } from ‘redux’;const store = createStore(reducer);

createStore 内部干了什么事儿呢?通过一个简单的 createStore 的实现,可以了解大概的原理(可以略过不看):

const createStore = (reducer) => {

let state;

let listeners = [];

const getState = () => state;

const dispatch = (action) => {

state = reducer(state, action);

listeners.forEach(listener => listener());

};

const subscribe = (listener) => {

listeners.push(listener);

return () => {

listeners = listeners.filter(l => l !== listener);

}

};

dispatch({});

return { getState, dispatch, subscribe };};

Redux 有很多的 Reducer,对于大型应用来说,State 必然十分庞大,导致 Reducer 函数也十分庞大,所以需要做拆分。Redux 里每一个 Reducer 负责维护 State 树里面的一部分数据,多个 Reducer 可以通过 combineReducers 方法合成一个根 Reducer,这个根 Reducer 负责维护整个 State。

import { combineReducers } from ‘redux’;// 注意这种简写形式,State 的属性名必须与子 Reducer 同名const chatReducer = combineReducers({

Reducer1,

Reducer2,

Reducer3})

combineReducers 干了什么事儿呢?通过简单的 combineReducers 的实现,可以了解大概的原理(可以略过不看):

const combineReducers = reducers => {

return (state = {}, action) => {

return Object.keys(reducers).reduce(

(nextState, key) => {

nextState[key] = reducers[key](state[key], action);

return nextState;

},

{}

);

};};

流程

再回顾一下刚才的流程图,尝试走一遍 Redux 流程:

1、用户通过 View 发出 Action:

store.dispatch(action);

2、然后 Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。Reducer 会返回新的 State 。

let nextState = xxxReducer(previousState, action);

3、State 一旦有变化,Store 就会调用监听函数。

store.subscribe(listener);

4、listener可以通过 store.getState() 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。

function listerner() {

let newState = store.getState();

component.setState(newState);

}

对比 Flux

和 Flux 比较一下:Flux 中 Store 是各自为战的,每个 Store 只对对应的 View 负责,每次更新都只通知对应的View:

Redux 中各子 Reducer 都是由根 Reducer 统一管理的,每个子 Reducer 的变化都要经过根 Reducer 的整合:

简单来说,Redux有三大原则:单一数据源:Flux 的数据源可以是多个。State 是只读的:Flux 的 State 可以随便改。* 使用纯函数来执行修改:Flux 执行修改的不一定是纯函数。

Redux 和 Flux 一样都是单向数据流。

中间件

刚才说到的都是比较理想的同步状态。在实际项目中,一般都会有同步和异步操作,所以 Flux、Redux 之类的思想,最终都要落地到同步异步的处理中来。

在 Redux 中,同步的表现就是:Action 发出以后,Reducer 立即算出 State。那么异步的表现就是:Action 发出以后,过一段时间再执行 Reducer。

那怎么才能 Reducer 在异步操作结束后自动执行呢?Redux 引入了中间件 Middleware 的概念。

其实我们重新回顾一下刚才的流程,可以发现每一个步骤都很纯粹,都不太适合加入异步的操作,比如 Reducer,纯函数,肯定不能承担异步操作,那样会被外部IO干扰。Action呢,就是一个纯对象,放不了操作。那想来想去,只能在 View 里发送 Action 的时候,加上一些异步操作了。比如下面的代码,给原来的 dispatch 方法包裹了一层,加上了一些日志打印的功能:

let next = store.dispatch;

store.dispatch = function dispatchAndLog(action) {

console.log(‘dispatching’, action);

next(action);

console.log(‘next state’, store.getState());}

既然能加日志打印,当然也能加入异步操作。所以中间件简单来说,就是对 store.dispatch 方法进行一些改造的函数。不展开说了,所以如果想详细了解中间件,可以点这里。

Redux 提供了一个 applyMiddleware 方法来应用中间件:

const store = createStore(

reducer,

applyMiddleware(thunk, promise, logger));

这个方法主要就是把所有的中间件组成一个数组,依次执行。也就是说,任何被发送到 store 的 action 现在都会经过thunk,promise,logger 这几个中间件了。

处理异步

对于异步操作来说,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(可能成功,也可能失败或者超时),这两个时刻都可能会更改应用的 state。一般是这样一个过程:

请求开始时,dispatch 一个请求开始 Action,触发 State 更新为“正在请求”状态,View 重新渲染,比如展现个Loading啥的。

请求结束后,如果成功,dispatch 一个请求成功 Action,隐藏掉 Loading,把新的数据更新到 State;如果失败,dispatch 一个请求失败 Action,隐藏掉 Loading,给个失败提示。

显然,用 Redux 处理异步,可以自己写中间件来处理,当然大多数人会选择一些现成的支持异步处理的中间件。比如 redux-thunk 或 redux-promise 。

Redux-thunk

thunk 比较简单,没有做太多的封装,把大部分自主权交给了用户:

const createFetchDataAction = function(id) {

return function(dispatch, getState) {

// 开始请求,dispatch 一个 FETCH_DATA_START action

dispatch({

type: FETCH_DATA_START,

payload: id

})

api.fetchData(id)

.then(response => {

// 请求成功,dispatch 一个 FETCH_DATA_SUCCESS action

dispatch({

type: FETCH_DATA_SUCCESS,

payload: response

})

})

.catch(error => {

// 请求失败,dispatch 一个 FETCH_DATA_FAILED action

dispatch({

type: FETCH_DATA_FAILED,

payload: error

})

})

}}//reducerconst reducer = function(oldState, action) {

switch(action.type) {

case FETCH_DATA_START :

// 处理 loading 等

case FETCH_DATA_SUCCESS :

// 更新 store 等

case FETCH_DATA_FAILED :

// 提示异常

}}

缺点就是用户要写的代码有点多,可以看到上面的代码比较啰嗦,一个请求就要搞这么一套东西。

Redux-promise

redus-promise 和 redux-thunk 的思想类似,只不过做了一些简化,成功失败手动 dispatch 被封装成自动了:

const FETCH_DATA = ‘FETCH_DATA’//action creatorconst getData = function(id) {

return {

type: FETCH_DATA,

payload: api.fetchData(id) // 直接将 promise 作为 payload

}}//reducerconst reducer = function(oldState, action) {

switch(action.type) {

case FETCH_DATA:

if (action.status === ‘success’) {

// 更新 store 等处理

} else {

// 提示异常

}

}}

刚才的什么 then、catch 之类的被中间件自行处理了,代码简单不少,不过要处理 Loading 啥的,还需要写额外的代码。

其实任何时候都是这样:封装少,自由度高,但是代码就会变复杂;封装多,代码变简单了,但是自由度就会变差。redux-thunk 和 redux-promise 刚好就是代表这两个面。

redux-thunk 和 redux-promise 的具体使用就不介绍了,这里只聊一下大概的思路。大部分简单的异步业务场景,redux-thunk 或者 redux-promise 都可以满足了。

上面说的 Flux 和 Redux,和具体的前端框架没有什么关系,只是思想和约定层面。下面就要和我们常用的 Vue 或 React 结合起来了:

Vuex

Vuex 主要用于 Vue,和 Flux,Redux 的思想很类似。

Store

每一个 Vuex 里面有一个全局的 Store,包含着应用中的状态 State,这个 State 只是需要在组件中共享的数据,不用放所有的 State,没必要。这个 State 是单一的,和 Redux 类似,所以,一个应用仅会包含一个 Store 实例。单一状态树的好处是能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

Vuex通过 store 选项,把 state 注入到了整个应用中,这样子组件能通过 this.\$store 访问到 state 了。

const app = new Vue({

el: ‘#app’,

// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件

store,

components: { Counter },

template: `

`})const Counter = {

template: <div>{{ count }}</div>,

computed: {

count () {

return this.$store.state.count

}

}}

State 改变,View 就会跟着改变,这个改变利用的是 Vue 的响应式机制。

Mutation

显而易见,State 不能直接改,需要通过一个约定的方式,这个方式在 Vuex 里面叫做 mutation,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。

const store = new Vuex.Store({

state: {

count: 1

},

mutations: {

increment (state) {

// 变更状态

state.count++

}

}})

触发 mutation 事件的方式不是直接调用,比如 increment(state) 是不行的,而要通过 store.commit 方法:

store.commit(‘increment’)

注意:mutation 都是同步事务。

mutation 有些类似 Redux 的 Reducer,但是 Vuex 不要求每次都搞一个新的 State,可以直接修改 State,这块儿又和 Flux 有些类似。具尤大的说法,Redux 强制的 immutability,在保证了每一次状态变化都能追踪的情况下强制的 immutability 带来的收益很有限,为了同构而设计的 API 很繁琐,必须依赖第三方库才能相对高效率地获得状态树的局部状态,这些都是 Redux 不足的地方,所以也被 Vuex 舍掉了。

到这里,其实可以感觉到 Flux、Redux、Vuex 三个的思想都差不多,在具体细节上有一些差异,总的来说都是让 View 通过某种方式触发 Store 的事件或方法,Store 的事件或方法对 State 进行修改或返回一个新的 State,State 改变之后,View 发生响应式改变。

Action

到这里又该处理异步这块儿了。mutation 是必须同步的,这个很好理解,和之前的 reducer 类似,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定先后顺序,A、B两个 mutation,调用顺序可能是 A -> B,但是最终改变 State 的结果可能是 B -> A。

对比Redux的中间件,Vuex 加入了 Action 这个东西来处理异步,Vuex的想法是把同步和异步拆分开,异步操作想咋搞咋搞,但是不要干扰了同步操作。View 通过 store.dispatch(‘increment’) 来触发某个 Action,Action 里面不管执行多少异步操作,完事之后都通过 store.commit(‘increment’) 来触发 mutation,一个 Action 里面可以触发多个 mutation。所以 Vuex 的Action 类似于一个灵活好用的中间件。

Vuex 把同步和异步操作通过 mutation 和 Action 来分开处理,是一种方式。但不代表是唯一的方式,还有很多方式,比如就不用 Action,而是在应用内部调用异步请求,请求完毕直接 commit mutation,当然也可以。

Vuex 还引入了 Getter,这个可有可无,只不过是方便计算属性的复用。

Vuex 单一状态树并不影响模块化,把 State 拆了,最后组合在一起就行。Vuex 引入了 Module 的概念,每个 Module 有自己的 state、mutation、action、getter,其实就是把一个大的 Store 拆开。

总的来看,Vuex 的方式比较清晰,适合 Vue 的思想,在实际开发中也比较方便。

对比Redux

Redux:view——>actions——>reducer——>state变化——>view变化(同步异步一样)

Vuex:view——>commit——>mutations——>state变化——>view变化(同步操作) view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

React-redux

Redux 和 Flux 类似,只是一种思想或者规范,它和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。

但是因为 React 包含函数式的思想,也是单向数据流,和 Redux 很搭,所以一般都用 Redux 来进行状态管理。为了简单处理 Redux 和 React UI 的绑定,一般通过一个叫 react-redux 的库和 React 配合使用,这个是 react 官方出的(如果不用 react-redux,那么手动处理 Redux 和 UI 的绑定,需要写很多重复的代码,很容易出错,而且有很多 UI 渲染逻辑的优化不一定能处理好)。

Redux将React组件分为容器型组件和展示型组件,容器型组件一般通过connect函数生成,它订阅了全局状态的变化,通过mapStateToProps函数,可以对全局状态进行过滤,而展示型组件不直接从global state获取数据,其数据来源于父组件。

如果一个组件既需要UI呈现,又需要业务逻辑处理,那就得拆,拆成一个容器组件包着一个展示组件。

因为 react-redux 只是 redux 和 react 结合的一种实现,除了刚才说的组件拆分,并没有什么新奇的东西,所以只拿一个简单TODO项目的部分代码来举例:

入口文件 index.js,把 redux 的相关 store、reducer 通过 Provider 注册到 App 里面,这样子组件就可以拿到 store 了。

import React from 'react’import { render } from 'react-dom’import { Provider } from 'react-redux’import { createStore } from 'redux’import rootReducer from './reducers’import App from './components/App’const store = createStore(rootReducer)

render(

,

document.getElementById(‘root’))

actions/index.js,创建 Action:

let nextTodoId = 0export const addTodo = text => ({

type: ‘ADD_TODO’,

id: nextTodoId++,

text})export const setVisibilityFilter = filter => ({

type: ‘SET_VISIBILITY_FILTER’,

filter})export const toggleTodo = id => ({

type: ‘TOGGLE_TODO’,

id})export const VisibilityFilters = {

SHOW_ALL: ‘SHOW_ALL’,

SHOW_COMPLETED: ‘SHOW_COMPLETED’,

SHOW_ACTIVE: ‘SHOW_ACTIVE’}

reducers/todos.js,创建 Reducers:

const todos = (state = [], action) => {

switch (action.type) {

case ‘ADD_TODO’:

return [

…state,

{

id: action.id,

text: action.text,

completed: false

}

]

case ‘TOGGLE_TODO’:

return state.map(todo =>

todo.id === action.id ? { …todo, completed: !todo.completed } : todo

)

default:

return state

}}export default todos

reducers/index.js,把所有的 Reducers 绑定到一起:

import { combineReducers } from 'redux’import todos from './todos’import visibilityFilter from './visibilityFilter’export default combineReducers({

todos,

visibilityFilter,

…})

containers/VisibleTodoList.js,容器组件,connect 负责连接React组件和Redux Store:

import { connect } from 'react-redux’import { toggleTodo } from '…/actions’import TodoList from '…/components/TodoList’const getVisibleTodos = (todos, filter) => {

switch (filter) {

case ‘SHOW_COMPLETED’:

return todos.filter(t => t.completed)

case ‘SHOW_ACTIVE’:

return todos.filter(t => !t.completed)

case ‘SHOW_ALL’:

default:

return todos

}}// mapStateToProps 函数指定如何把当前 Redux store state 映射到展示组件的 props 中const mapStateToProps = state => ({

todos: getVisibleTodos(state.todos, state.visibilityFilter)})// mapDispatchToProps 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。const mapDispatchToProps = dispatch => ({

toggleTodo: id => dispatch(toggleTodo(id))})export default connect(

mapStateToProps,

mapDispatchToProps)(TodoList)

简单来说,react-redux 就是多了个 connect 方法连接容器组件和UI组件,这里的“连接”就是一种映射:mapStateToProps 把容器组件的 state 映射到UI组件的 props mapDispatchToProps 把UI组件的事件映射到 dispatch 方法

Redux-saga

刚才介绍了两个Redux 处理异步的中间件 redux-thunk 和 redux-promise,当然 redux 的异步中间件还有很多,他们可以处理大部分场景,这些中间件的思想基本上都是把异步请求部分放在了 action creator 中,理解起来比较简单。

redux-saga 采用了另外一种思路,它没有把异步操作放在 action creator 中,也没有去处理 reductor,而是把所有的异步操作看成“线程”,可以通过普通的action去触发它,当操作完成时也会触发action作为输出。saga 的意思本来就是一连串的事件。

redux-saga 把异步获取数据这类的操作都叫做副作用(Side Effect),它的目标就是把这些副作用管理好,让他们执行更高效,测试更简单,在处理故障时更容易。

在聊 redux-saga 之前,需要熟悉一些预备知识,那就是 ES6 的 Generator。

如果从没接触过 Generator 的话,看着下面的代码,给你个1分钟傻瓜式速成,函数加个星号就是 Generator 函数了,Generator 就是个骂街生成器,Generator 函数里可以写一堆 yield 关键字,可以记成“丫的”,Generator 函数执行的时候,啥都不干,就等着调用 next 方法,按照顺序把标记为“丫的”的地方一个一个拎出来骂(遍历执行),骂到最后没有“丫的”标记了,就返回最后的return值,然后标记为 done: true,也就是骂完了(上面只是帮助初学者记忆,别喷~)。

function* helloWorldGenerator() {

yield ‘hello’;

yield ‘world’;

return ‘ending’;}var hw = helloWorldGenerator();

hw.next() // 先把 ‘hello’ 拎出来,done: false 代表还没骂完// { value: ‘hello’, done: false } next() 方法有固定的格式,value 是返回值,done 代表是否遍历结束

hw.next() // 再把 ‘world’ 拎出来,done: false 代表还没骂完// { value: ‘world’, done: false }

hw.next() // 没有 yield 了,就把最后的 return ‘ending’ 拎出来,done: true 代表骂完了// { value: ‘ending’, done: true }

hw.next() // 没有 yield,也没有 return 了,真的骂完了,只能挤出来一个 undefined 了,done: true 代表骂完了// { value: undefined, done: true }

这样搞有啥好处呢?我们发现 Generator 函数的很多代码可以被延缓执行,也就是具备了暂停和记忆的功能:遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值,等着下一次调用next方法时,再继续往下执行。用 Generator 来写异步代码,大概长这样:

function* gen(){

var url = ‘https://api.github.com/users/github’;

var jsonData = yield fetch(url);

console.log(jsonData);}var g = gen();var result = g.next();

// 这里的result是 { value: fetch(‘https://api.github.com/users/github’), done: true }// fetch(url) 是一个 Promise,所以需要 then 来执行下一步

result.value.then(function(data){

return data.json();}).then(function(data){

// 获取到 json data,然后作为参数调用 next,相当于把 data 传给了 jsonData,然后执行 console.log(jsonData);

g.next(data);});

再回到 redux-saga 来,可以把 saga 想象成开了一个以最快速度不断地调用 next 方法并尝试获取所有 yield 表达式值的线程。举个例子:

// saga.jsimport { take, put } from 'redux-saga/effects’function* mySaga(){

// 阻塞: take方法就是等待 USER_INTERACTED_WITH_UI_ACTION 这个 action 执行

yield take(USER_INTERACTED_WITH_UI_ACTION);

// 阻塞: put方法将同步发起一个 action

yield put(SHOW_LOADING_ACTION, {isLoading: true});

// 阻塞: 将等待 FetchFn 结束,等待返回的 Promise

const data = yield call(FetchFn, ‘https://my.server.com/getdata’);

// 阻塞: 将同步发起 action (使用刚才返回的 Promise.then)

yield put(SHOW_DATA_ACTION, {data: data});}

这里用了好几个yield,简单理解,也就是每个 yield 都发起了阻塞,saga 会等待执行结果返回,再执行下一指令。也就是相当于take、put、call、put 这几个方法的调用变成了同步的,上面的全部完成返回了,才会执行下面的,类似于 await。

用了 saga,我们就可以很细粒度的控制各个副作用每一部的操作,可以把异步操作和同步发起 action 一起,随便的排列组合。saga 还提供 takeEvery、takeLatest 之类的辅助函数,来控制是否允许多个异步请求同时执行,尤其是 takeLatest,方便处理由于网络延迟造成的多次请求数据冲突或混乱的问题。

saga 看起来很复杂,主要原因可能是因为大家不熟悉 Generator 的语法,还有需要学习一堆新增的 API 。如果抛开这些记忆的东西,改造一下,再来看一下代码:

function mySaga(){

if (action.type === ‘USER_INTERACTED_WITH_UI_ACTION’) {

store.dispatch({ type: ‘SHOW_LOADING_ACTION’, isLoading: true});

const data = await Fetch(‘https://my.server.com/getdata’);

store.dispatch({ type: ‘SHOW_DATA_ACTION’, data: data});

}}

上面的代码就很清晰了吧,全部都是同步的写法,无比顺畅,当然直接这样写是不支持的,所以那些 Generator 语法和API,无非就是做一些适配而已。

saga 还能很方便的并行执行异步任务,或者让两个异步任务竞争:

更多面试题

**《350页前端校招面试题精编解析大全》**内容大纲主要包括 HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

起 action (使用刚才返回的 Promise.then)

yield put(SHOW_DATA_ACTION, {data: data});}

这里用了好几个yield,简单理解,也就是每个 yield 都发起了阻塞,saga 会等待执行结果返回,再执行下一指令。也就是相当于take、put、call、put 这几个方法的调用变成了同步的,上面的全部完成返回了,才会执行下面的,类似于 await。

用了 saga,我们就可以很细粒度的控制各个副作用每一部的操作,可以把异步操作和同步发起 action 一起,随便的排列组合。saga 还提供 takeEvery、takeLatest 之类的辅助函数,来控制是否允许多个异步请求同时执行,尤其是 takeLatest,方便处理由于网络延迟造成的多次请求数据冲突或混乱的问题。

saga 看起来很复杂,主要原因可能是因为大家不熟悉 Generator 的语法,还有需要学习一堆新增的 API 。如果抛开这些记忆的东西,改造一下,再来看一下代码:

function mySaga(){

if (action.type === ‘USER_INTERACTED_WITH_UI_ACTION’) {

store.dispatch({ type: ‘SHOW_LOADING_ACTION’, isLoading: true});

const data = await Fetch(‘https://my.server.com/getdata’);

store.dispatch({ type: ‘SHOW_DATA_ACTION’, data: data});

}}

上面的代码就很清晰了吧,全部都是同步的写法,无比顺畅,当然直接这样写是不支持的,所以那些 Generator 语法和API,无非就是做一些适配而已。

saga 还能很方便的并行执行异步任务,或者让两个异步任务竞争:

更多面试题

**《350页前端校招面试题精编解析大全》**内容大纲主要包括 HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

[外链图片转存中…(img-jrt1u5xf-1714515256206)]

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 我可以提供一些建议,但不保证最终的结果会是什么样的:1. 要确保代码的可读性,使用清晰的变量名,添加必要的注释;2. 尽量使用语义化的标签和类名;3. 使用有效的 HTML 结构,例如表格、列表等;4. 尽量使用最新的 HTML 元素和属性;5. 遵循 W3C 标准,确保前端页面的跨浏览器兼容性。 ### 回答2: 前端的增删改查是指在网页中实现数据的添加、删除、修改和查询功能。在页面写方面,需要运用HTML、CSS和JavaScript等前端技术来实现。 首先是数据的展示和查询。通过HTML来搭建页面结构,使用CSS来设置样式,布局和美化页面。为了实现数据的查询功能,可以使用JavaScript编写相关的代码,通过事件绑定和DOM操作来获取用户输入的查询条件,并发送请求到后端进行数据的查询。 接下来是数据的添加。可以在页面中添加一个表单,通过HTML的form标签来创建表单元素,设置相关的字段和按钮。使用JavaScript编写事件监听,监听表单的提交事件,获取用户输入的数据,然后通过Ajax技术将数据发送到后端进行添加操作。 在删除数据方面,可以在页面中每一行数据的最后一列增加一个删除按钮。当点击删除按钮时,通过JavaScript编写事件监听,获取当前行的数据ID,并通过Ajax技术将该ID发送到后端进行删除操作。 最后是数据的修改。在展示数据的每一行中添加一个修改按钮,同样通过JavaScript编写事件监听,获取当前行的数据ID和其他需要修改的内容,通过Ajax技术将这些数据发送到后端进行修改操作。 总之,前端的增删改查页面写需要使用HTML、CSS和JavaScript等技术来搭建页面结构、设置样式,并通过事件监听和DOM操作实现数据的查询、添加、删除和修改功能。 ### 回答3: 前端增删改查(CRUD)是开发网页应用程序时常见的操作,它涉及到页面写的各个方面。 首先,增加数据的页面写需要包括一个表单,用于用户输入新增数据的相关信息。表单通常会包含各种输入框(如文本框、下拉框、复选框等)以及提交按钮。通过使用HTML和CSS,可以编写出具有良好样式和布局的表单页面。另外,在JavaScript的帮助下,还可以对表单做一些验证逻辑,确保用户输入的数据的合法性。 其次,删除数据的页面写需要考虑用户交互的方式。可以使用按钮或链接来触发删除操作,并通过JavaScript代码与后端进行交互,以完成数据的删除。同时,为了防止误操作,可以弹出确认框,让用户再次确认删除操作。 再次,修改数据的页面写需要与增加数据的页面相似,也需要包括一个表单,用于用户修改相应数据的信息。不同的是,表单中的初始值应该是当前数据的原始值,以便用户进行修改。在提交修改时,通过JavaScript代码将修改后的数据发送给后端。 最后,查询数据的页面写需要提供查询条件的输入框或下拉框供用户选择。用户可以按照特定的条件进行搜索,并通过JavaScript等技术将查询条件发送到后端,后端根据条件返回相应的数据结果。同时,可以在页面上展示查询结果,提供更好的用户体验。 总之,前端增删改查页面的写需要充分考虑用户交互、数据传输和页面展示等方面的设计。通过合理运用HTML、CSS和JavaScript等前端技术,可以构建出用户友好、功能完善的增删改查页面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值