Web前端最全实例讲解基于 React+Redux 的前端开发流程(2),老师讲的真棒

性能优化

1.webpack打包文件体积过大?(最终打包为一个js文件)

2.如何优化webpack构建的性能

3.移动端的性能优化

4.Vue的SPA 如何优化加载速度

5.移动端300ms延迟

6.页面的重构

所有的知识点都有详细的解答,我整理成了280页PDF《前端校招面试真题精编解析》。

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

]

};

//是否开启模拟数据的开关,注释这行代码关闭模拟数据

state = mockState;

//对 state 做显示级别的转化

let stats = {

appleNow: {

quantity: 0,

weight: 0

},

appleEaten: {

quantity: 0,

weight: 0

}

};

state.apples.map(apple => {

let selector = apple.isEaten ? ‘appleEaten’:‘appleNow’;

stats[selector].quantity ++;

stats[selector].weight += apple.weight;

})

return (

苹果篮子
当前

{stats.appleNow.quantity}个苹果,

{stats.appleNow.weight}克

已吃掉

{stats.appleEaten.quantity}个苹果,

{stats.appleEaten.weight}克

{ state.apples.map(apple => ) }

摘苹果

);

}

}

function select(state) {

return {

state: state.appleBusket

}

}

export default connect(select)(AppleBusket);

可见,动态布局的工作要求只是在 HTML + CSS 布局的基础上,再加上 JSX 语法能力即可。

2. 普通显示组件的动态渲染

普通显示组件的动态渲染和容器类似,只是这里的state可以自己规定,并给出示例的mockState(模拟state),使用组件的人按照示例传入数据即可使用。

AppleItem.jsx 的更新如下:

import React from 'react'; class AppleItem extends React.Component { shouldComponentUpdate(nextProps) { return nextProps.state != this.props.state; } render() { let { state, actions } = this.props; /** * 这个区域是 mock 数据区,也作为组件文档,请书写清楚 * //在组件发布时,请注释掉,提高性能 */ let mockState = { id: 1, weight: 256, isEaten: false }; let mockActions = { eatApple : id => console.log('eatApple',id) }; /** * 开关这行代码,用于切换装入的数据来源。(为了开关的方便,请把两句代码合成一行) * 在开发阶段打开,使用内部 state 和 action, 开发完成后请注释关闭 */ state = mockState; actions = mockActions; if (state.isEaten) return null; return ( <div className="appleItem"> <div className="apple"><img src="../images/apple.png" alt=""/></div> <div className="info"> <div className="name">红苹果 - {state.id}号</div> <div className="weight">{state.weight}克</div> </div> <div className="btn-div"><button onClick={() => actions.eatApple(state.id) }>吃掉</button></div> </div> ); } } export default AppleItem;

容器和普通显示组件state的对比:

  1. 容器的state我们是从store中的总state直接获得的,注意 AppleBusket.jsx 靠后面这段代码:

function select(state) {

return {

state: state.appleBusket

}

}

select是一个state筛选器, 功能是选择总state中的 appleBusket 作为本容器的state。而这个state的格式我们会在其对应的reducer中规定(因为我们需要在reducer中提供对应state的默认值)

  1. 普通显示组件的state格式由组件开发人员自己约定即可,并在mockState 区域给出例子。当别人要使用你的显示组件时,必须根据你规定的格式传入state数据。在组件里面,我们一般会实现如下这样一个自动切换器,当组件的使用者在使用该组件时没有传入state, 就会显示内部的模拟state,为使用者带来预览效果。

if(state === undefined) state = mockState;

以上就是布局组的开发工作: 静态布局 + 动态布局。前者只需要会html+css布局即可,后者还要会一些jsx的语法和基本的js语法。

逻辑组


任务1:action 开发

任务内容:开发 redux 流程的 action,并把action部署到对应容器和组件中。

技能要求:需要对js比较熟悉,并要求要会使用jq的ajax功能。

首先,我们先来看看 action 的定义。

1. 狭义的 action

狭义的action是指一个简单的对象,对象的结构如下,只要在对象内包含type属性指明action的名称即可,同时,在对象的其他属性里,可以以任何形式自由保存其他相关数据。

let action = {

type: ‘ACTION_NAME’,

}

一般 type 的内容使用 大写字母+下划线 的格式。

在这个定义的基础上,业界提出以一种标准 action, 叫做 Flux Standard Action , 该标准下的action除了type属性之外,只允许多加(不是必须包含)这三个属性:payload,error,meta。

let FSA = {

type: ‘ACTION_NAME’,

payload: <bool | number | string | object>, //action的负载,可以是数据或 error 对象

error: , // 指明该action是否是一个以 error 为负载的action

meta: // action元数据, 包含解释该action含义的信息

}

我们宅印约定都要使用 Flux Standard Action,下面是吃苹果 action:

let FSA = {

type: ‘EAT_APPLE’,

payload: 3, // 负载是3, 说明吃掉3号苹果, 这里也可以写成 { id : 3 }

meta: ‘This action will eat an apple!’ // (不是必须的)

}

一个action只是一个对象,他需要根据时机被 store 的 dispatch 函数调用才会开始进行处理:store.dispatch(action_1)

2. 广义的 action

广义的 action 是指在中间件的支持下,dispatch 函数可以调用的数据类型,除了普通action之外,常见的有 thunk, promise 等。我们用常用的 thunk来举个例子。

thunk 其实就是一个代码片段,可以简单理解为一种特定的函数,我们可以dispatch 这个thunk。 thunk函数具有如下的签名

(dispatch, getState) => { //在函数体内可以使用 dispatch 方法来发射其他 action //在函数体内可以使用 getState 方法来获取当前的state }

下面是一个我们摘苹果动作的例子:

let pickAppleAction = (dispatch, getState) => { ajax({ url: '/pickApple', method: 'GET', }).done(data => { //发射普通 action dispatch({ type: 'DONE_PICK_APPLE', payload: data.weight // 或者 payload: {weight: data.weight} }); }).fail(xhr => { //发射普通 action, 其负载是一个error dispatch({ type: 'FAIL_PICK_APPLE', payload: new Error(xhr.responseText) , error: true }); }) }

定义好之后,我们可以直接这样调用这个thunk:

dispatch( pickAppleAction )

接下来,我们来做进一步优化,把action统一为actionCreator , 我们不难察觉,每次都要书写{ type: ‘ACTION_NAME’ … } 是很麻烦也很容易出错的,actionCreator 就是为解决这个问题而生的,actionCreator 就是一个产生特定action(这里指广义的action)的函数,下面来看简单的actionCreator 例子:

//传统写法 var eatApple = function(id) { return { type: 'EAT_APPLE', payload: id } } // es6 写法 let eatApple = id => ({ type: 'EAT_APPLE', payload: id })

这样一来,一方面是使用起来比较简单方便,另一方面是具有文档作用。

只需要这样发射action就可以啦:

dispatch(eatApple(3))

普通action的actionCreator封装工作, 可以使用 redux-actions 自动完成, 查看其文档就可以快速上手,可以根据情况使用。

在项目中,我们会为每个板块写一个的action文件,并统一使用actionCreator, 所以最终 appleAction.js 如下:

import ajax from ‘…/services/ajax’; //经过封装的加强型 ajax 函数 //这是名空间,对普通action做划分 const prefix = ‘apple/’; let actions = { //注意这里需要 () => … , 不然 pickAppleAction 不是一个actionCreator, 而是一个thunk pickApple: () => (dispatch, getState) => { //如果正在摘苹果,则结束这个thunk, 不执行摘苹果 if(getState().isPicking) return; //通知开始摘苹果 dispatch(actions.beginPickApple()); //发送摘苹果请求 ajax({ url: ‘/appleBasket/pickApple’, method: ‘GET’ }).done(data => { dispatch(actions.donePickApple(data.weight)) }) .fail(xhr => { dispatch(actions.failPickApple(xhr.responseText)); }) }, beginPickApple: () => ({ type: ‘apple/BEGIN_PICK_APPLE’ }), donePickApple: appleWeight => ({ type: ‘apple/DONE_PICK_APPLE’, payload: appleWeight }), failPickApple: errMsg => ({ type: ‘apple/FAIL_PICK_APPLE’, payload: new Error(errMsg), error: true }), eatApple: appleId => ({ type: ‘apple/EAT_APPLE’, payload: appleId }) }; export default actions;

这样一来,只要引入  appleAction.js ,就可以快速使用定义好的action,结合某些编辑器的智能提示功能,非常方便,下面是 vsCode 编辑器的效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

写好了action之后,只要在 container 的对应位置装上action就好了, 下面是appleBasket.jsx 总体代码:

import React from ‘react’;

import { connect } from ‘react-redux’;

import AppleItem from ‘…/components/AppleItem’;

import actions from ‘…/actions/appleActions’;

class AppleBusket extends React.Component {

render() {

let { state, dispatch } = this.props;

return (

{ state.apples.map(apple =>

<AppleItem

state ={apple}

actions={{eatApple: id => dispatch(actions.eatApple(id))}}

key={apple.id}

/>

) }

<button onClick={() => dispatch(actions.pickApple())}>摘苹果

)

}

}

function selectState(state) {

return {

state: state.appleBusket

}

}

export default connect(selectState)(AppleBusket);

注意这两行。就是装入action的地方

actions={{eatApple: id => dispatch(actions.eatApple(id))}}

<button onClick={() => dispatch(actions.pickApple())}>摘苹果

上面代码中引入的actions其实是actionCreators。

此外,actionCreator还有很简洁的用法:对actionCreator做dispatch级别的封装,这个过程我们可以使用 redux 提供的 bindActionCreators 函数自动完成。然后就可以直接调用action,而不需要使用dispatch方法去调用actionCreator。看下面更新后的代码:

import React from ‘react’;

import { bindActionCreators } from ‘redux’;

import { connect } from ‘react-redux’;

import AppleItem from ‘…/components/AppleItem’;

import actions from ‘…/actions/appleActions’;

class AppleBusket extends React.Component {

render() {

let { state, actions} = this.props;

return (

{ state.apples.map(apple =>

<AppleItem

state ={apple}

actions={{eatApple: actions.eatApple}}

key={apple.id}

/>

) }

摘苹果

)

}

}

function selectState(state) {

return {

state: state.appleBusket

}

}

function buildActionDispatcher(dispatch) {

return {

actions: bindActionCreators(actions, dispatch)

}

}

export default connect(selectState, buildActionDispatcher)(AppleBusket);

注意这三个变动:

let { state, actions } = this.props;

actions={{eatApple: actions.eatApple }}

摘苹果

我们对比一下之前的写法:

let { state, dispatch } = this.props

actions={{eatApple: id => dispatch(actions.eatApple(id))}}

<button onClick={() => dispatch(actions.pickApple())}>摘苹果

可以发现使用新的方式使代码简洁了很多!

但是,这对于有对象属性提示功能编辑器来说,这种方式会使编辑器无法分析对象属性:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这时,需要一边看actions文件对该actions对象的定义,一边在目标位置填入action,不过这也不是很麻烦。而且对于使用没有对象属性提示的编辑器的开发者来说,这个 drawback 根本就不存在。我们相对推荐使用这种经过dispatch封装的action, 但不要求,大家根据自己的情况使用即可。

对于普通显示组件

对于普通显示组件的actions传递方式,我们统一使用actions属性传递,如下:

AppleBasket.jsx

<AppleItem

state ={apple}

actions={{eatApple: actions.eatApple }}

key={apple.id}

/>

AppleItem.jsx

<button onClick={() => actions.eatApple(state.id)}>吃掉

普通显示组件使用统一actions属性接受父级的action,可以在组件内部建立mockActions, 这个mockActions 既有文档功能,也有测试功能,非常实用:

let mockActions = { eatApple : id => console.log('eatApple',id), //指定了函数的签名 foo: (arg1,arg2) => console.log('foo',arg1,arg2) //也响应了调用测试 }; /** * 开关这行代码,用于切换装入的state和actions来源。(为了开关的方便,请把两句代码合成一行) * 在开发阶段打开,使用内部 state 和 action, 开发完成后请注释关闭 */ state = mockState; actions = mockActions;

点击 “摘苹果” 和 “吃掉” 按钮,我们看看控制台,已经可以发出我们想要的action啦:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

好啦,actions 开发的内容就介绍到这里。我们来总结一下我们对action所做的封装:

action -> actionCreator -> actionDispatcher

任务2:reducer 开发

开发内容: reducer的其实就是action的处理器。其开发的内容很明确清晰,就是开发一类函数,接受action 和 当前的state,返回新的state。

技术要求:要求对js比较熟悉,需要会使用 immutable.js 这个数据静态化库。

下面是reducer功能的图解:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们先看看我们苹果板块的state的数据结构,非常简单,这里是某个时刻的状态:

{

isPicking : false,

newAppleId: 1,

apples: [

{

id: 0,

weight: 235,

isEaten: false

}

]

}

有三个一级属性:

  • isPicking :表示是否正在摘苹果,我们在上面已经知道,摘苹果其实是发送一个ajax请求,向后台摘一个苹果,这个请求在进行时我们会把 isPicking 设置为ture, 表明正在摘苹果,同时禁止在完成前再发送摘苹果请求

  • newAppleId:表示新苹果的编号

  • apples:是苹果列表数组,存放着苹果对象,苹果对象的结构在apples数组里有给出实例。

我们上面提及actions分为广义的action和狭义的普通action。其实,非普通action, 如thunk,一般会以发起普通action结束。我们reducer只需要处理狭义上的普通action,。在我们摘苹果应用里,总共有这4个普通action:

//通知store应用开始摘苹果 beginPickApple: () => ({ type: 'apple/BEGIN_PICK_APPLE' }), //摘苹果成功 donePickApple: appleWeight => ({ type: 'apple/DONE_PICK_APPLE', payload: appleWeight }), //摘苹果失败 failPickApple: error => ({ type: 'apple/FAIL_PICK_APPLE', payload: error, error: true }), //吃苹果 eatApple: appleId => ({ type: 'apple/EAT_APPLE', payload: appleId })

下面是根据action,写出的 reducer 的基本结构:

(state = defaultState, action) => { switch (action.type) { case 'apple/BEGIN_PICK_APPLE': //TODO return state; case 'apple/DONE_PICK_APPLE': //TODO return state; case 'apple/FAIL_PICK_APPLE': //TODO return state; case 'apple/EAT_APPLE': //TODO return state; default: return state; } };

我们可以看到,reducer是一个函数,接受state和action两个参数,在函数内部,根据 action.type 来确定要做哪些操作,并且每种操作都要返回state(或者是新的,或者是原来的)。

我们以 apple/EAT_APPLE动作为例,讲解如何书写reducer。EAT_APPLE 动作的含义是吃苹果,我们可以非常简单地处理这个动作:直接把对应苹果对象的 isEaten 属性设为true即可。

按照一般的思维,我们会这样处理:

case ‘apple/EAT_APPLE’:

state.apples[action.payload].isEaten = true;

return state;

但是,这种方法在 redux 应用里看不到作用,因为这种写法不会使store触发react进行重新渲染,为什么呢?因为 newState == oldState ! 下面我们来做一些解释:

首先,要先从js对象的相等判断运算说起,我们看下面的代码

let a = { foo: ‘bar’};

let b = { foo: ‘bar’};

console.log( a == b ); //结果是 false

a 和 b 看起来一样,但为什么是false呢?因为对象和数组的赋值是引用赋值, a 和 b 只是一个引用符号,其所指向的对象实体不同(比如 a -> object#001, b -> object#002),js的对象(数组)相等判断是根据是否指向同一个对象实体来的确定的 (object#001 ?= object#002 // false),详见 MDN

再看看下面的例子:

let a = {foo: ‘bar’};

let b = a;

b.foo = ‘good’;

console.log( a == b ); //结果是 true

现在应该可以理解刚才为什么newState == oldState了吧~

redux 是根据返回的state是否改变来决定是否通知 react 更新的。根据这种情况所,可能有人会这样改进刚才的reducer:

state.apples[action.payload].isEaten = true; newState = Object.assign({},state); return newState;

这样一来,点击 “吃掉”按钮,看到了有效果了,苹果不见了!但是,这种写法只是迎合了redux更新视觉组件的触发条件,还具有很大的局限性,不是我们redux规定的reducer。下面我们来看看正确的reducer:

首先,reducer有这样的重要约束:在reducer里,不可以修改原来的state,需要保持使每个版本的state不变。

这种保持数据不变(Persistent data structure)的方式在函数式编程(Functional programming)非常常见。在我们的redux应用里,其意义在于:

1. 调试意义:保持每个版本的state的不变性,使得我们可以跟踪每个时刻的state, 跟踪应用的“发展史”,这个特性为调试带来了很大的方便。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. 性能意义:保持state不变这个约束引导我们使用局部更新对象的方法,这样会可以非常有效地提高react或其他显示框架的渲染效率。我们先来看看为了保持数据不变性,要怎么对state做更新,以我们的苹果篮子state为例:

例子:通知开始摘苹果:apple/BEGIN_PICK_APPLE

为了保证每个版本的state不变性,我们有两种实现方式:“深复制”,“浅复制”。我们来看两种模式的内部原理:

深复制方式:有人会这样想:“保持state的不变性很容易,只需要深复制一个state, 让后在新的state要怎么修改就怎么修改,不ok了吗?”,如下就是深复制

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这种方式是一种很低级保持不变性的方式:

  1. 深复制操作运行效率低

  2. 没有为渲染环节提供提高渲染效率的铺垫

它只是简单迎合保持数据不变性的约束,虽然有一定调试意义,但是,不但没有提高程序的性能,反而降低了程序的总体性能!没有实践意义。

浅复制方式:浅复制模式只对内部数据发生变化的引用做更新,如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

“state” 对象的内部数据发生变化,所以创建新的state引用;而apples array 内部数据不发生变化,所以就不对该引用做更新!在这个操作中,这种浅复制的方法运行效率比较高,而且其简单地实现了数据不变性,为调试带来方便,同时,也是更重要的,这种浅复制的方式极大地提高了视觉组件渲染阶段的运行效率!我们来对比一下:当用户点击摘苹果时,如果使用“深复制”,渲染程序需要重新遍历整个state对象树来做视觉更新,而使用浅复制来实现数据不变性时,渲染程序只需要遍历state对象的一级子节点即可,而不需要对apples array 做遍历,性能大大地提高。尤其是当苹果很多的时候,两种方式的性能差距是非常明显的。

备注:在react组件里面,要开启条件更新这个生命周期函数 shouldComponentUpdate, 才会对把这个性能提高点释放出来,类似这样:

shouldComponentUpdate(nextProps) {

return nextProps.state != this.props.state;

}

下面我们再给出 “吃苹果” reducer 进行浅复制的例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

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

资料截图 :

高级前端工程师必备资料包

state对象的一级子节点即可,而不需要对apples array 做遍历,性能大大地提高。尤其是当苹果很多的时候,两种方式的性能差距是非常明显的。

备注:在react组件里面,要开启条件更新这个生命周期函数 shouldComponentUpdate, 才会对把这个性能提高点释放出来,类似这样:

shouldComponentUpdate(nextProps) {

return nextProps.state != this.props.state;

}

下面我们再给出 “吃苹果” reducer 进行浅复制的例子:

[外链图片转存中…(img-AChF651i-1715187881967)]

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

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

资料截图 :

高级前端工程师必备资料包

  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端开发中的React Redux全家桶是一套常用的技术栈,用于构建复杂的Web应用程序。React是一个由Facebook开发的JavaScript库,用于构建用户界面。它通过将应用程序拆分成可重用的组件,使开发人员能够更轻松地开发、测试和维护Web应用程序。 Redux是一个用于管理应用程序状态的库。它采用了一种称为单一状态树的模式,将整个应用程序的状态存储在一个对象中,并使用纯粹的函数来修改状态。Redux的核心概念包括:store、reducer和action。Store是应用程序的状态容器,reducer是一个纯函数,用于根据action来修改状态,而action代表用户触发的操作。 React Redux是将ReactRedux结合在一起使用的库。通过使用React Redux,我们可以将Redux的状态管理功能集成到React组件中。React Redux提供了一种称为容器组件的机制,它负责从Redux store中提取数据,并将其作为props传递给展示组件。这种分离开发的模式使得代码更加模块化和易于维护。 React Redux全家桶还包括一些其他的辅助库,如React Router用于跟踪和管理应用程序的URL路径,以及Redux Thunk或Redux Saga用于处理异步操作。这些库的整合和使用能够帮助开发人员构建可扩展、高效和易于维护的前端应用程序。 总之,前端开发中的React Redux全家桶提供了一套完善的工具和库,帮助开发人员构建复杂的Web应用程序。它能够将状态管理和用户界面开发结合在一起,并提供了一种模块化和分离开发的解决方案。通过学习和使用React Redux全家桶,开发人员可以提高开发效率,并构建出更好的用户体验。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值