以组件的方式构建UI
- 受控组件:表单元素状态由使用者维护,一定有value和onChange属性
- 非受控组件:表单元素状态由DOM自身维护,外部方法才能获得表单元素的值
创建组件的原则
- 单一职责原则
每个组件只做一件事情
如果组件变得复杂,就应该拆分小组件:可以提高性能,当数据变化时,复杂大组件可能会整体刷新,而拆分后部分小组件如果没有变化则不用刷新。 - 数据状态管理:DRY原则
- 能计算得到的状态不要单独存储
- 组件内尽量无状态,所需数据通过props获取:纯组件性能更好,更容易被重用
React的生命周期
- constructor
JS类的构造函数。可以在这个函数中初始化组件内部的状态,实际中很少使用;唯一可以直接修改state的地方。 - getDerivedStateFromProps
当state需要从props初始化得到时使用;
尽量不要使用:维护state和props的一致性会增加复杂度;
每次render都会调用;
典型场景:获取表单输入的默认值 - componentDidMount
UI渲染完后只执行一次;
典型场景:获取外部资源 - getSnapshotBeforeUpdate
在render之前调用,此时state已经更新;
典型场景:获取render之前的DOM状态 - shouldComponentUpdate
决定Virtual DOM是否要重绘;
一般可以由pureComponent自动实现(看之前的state、props和之后的state、props是否一致);
典型场景:性能优化;
理解Virtual DOM及key的作用
diff算法
react的diff算法把传统的完全diff的O(n^3)复杂度优化为了O(n),是一种广度优先的分层算法
1.当同层的节点顺序发生变化时,根据节点的key来判断顺序变化,并更换节点顺序;
2.当同层的节点没有被同层引用,则直接删除节点
对于这种设计方式较为合理,因为节点出现跨层移动的概率较低。
虚拟DOM的两个假设:
1.组件的DOM结构相对稳定(即节点出现跨层移动的概率较低)
2.类型系统的兄弟节点可以被唯一标识(即节点的key)
如果没有key,可能会使得diff算法更新的方式性能开销更大
高阶组件和函数作为子组件:组件使用的一种新设计模式
- 高阶组件
高阶组件就是对原有的组件进行处理,如加入新的属性、方法等。高阶组件的入参为组件,出参为处理之后的组件。它可以用来处理组件之间的一些通用逻辑,但是自身并不包含任何UI展现。 -
函数作为子组件
是一种设计模式。组件的this.props.children(this.props.value)可以执行复用这个组件时里面定义的不同的函数。
因此,复用「函数作为子组件」的组件可以具体render的内容可以由组件传入的函数决定,而不是组件本身不断增加自己的功能。这使得组件的灵活性增大。
Context API及其使用场景
解决组件通信的问题。当context数据发生变化时会通知consumer进行数据刷新,而不用用forceUpdate在数据变化后手动执行强制刷新。
使用场景:全剧更新theme或language等
使用脚手架工具创建react应用
- 为什么需要脚手架工具
当创建一个react项目时,往往需要很多别的工具,比如redux管理数据状态、babel将ts等语言转换为浏览器可以识别的语言、webpack进行打包、eslint作为代码校验等,因此在最初开始一个react往往有着比较繁杂的步骤,但是这些步骤在每一次创建时往往都是重复的,仅仅需要改一些配置即可。因此诞生了脚手架工具。
打包和部署
- 为什么需要打包
编译ES6语法特性,编译JSX;
整合资源,例如图片、scss等;
优化代码体积; - 打包注意事项
设置nodejs环境为production;
禁用开发时专用代码;
设置应用根路径;
Redux:JS状态管理框架
传统上state转换为DOM是在组件内部发生的;
redux则是把这个转换转移到了组件外,它提供一个全局唯一的store,这个store负责提供应用程序所有的state。是一个树形结构,和组件的树形结构是映射状态。redux也是为了让组件通信更便捷。
redux的三个特性
如上图,Single Source of Truth让数据state更容易追踪;
如上图,state发生变化一定是由action引起的;
如上图,纯函数是不引用外部任何变量或方法的函数;
深入理解Store、Action、Reducer
action是描述行为的数据结构;
一个reducer是一个函数,所有的reducer会接收到所有的action,一个action通过store dispatch出去,系统中定义的reducer都能接收到,而要不要执行则通过action.type决定。reducer执行后是直接返回新的state对象,而不是修改原有的state,否则store无法监听到变化;
工具函数
- bindActionCreators
一个可以封装dispatch具体action的工具函数,让action的dispatch动作可以在任何地方被调用; - combineReducers
createStore要接收reducer来初始化,而combineReducers就是把多个reducer组合成一个新的reducer传给createStore,并定义每个reducer对应的state节点。
在react中使用redux
react和redux通过一个函数connect连接起来:
使用步骤
1.通过mapStateToProps定义组件需要访问store中的哪些属性;
2.通过mapDispatchToProps定义组件需要哪些dispatch操作来更改state值;
3.调用connect方法并使用;
理解异步Action和Redux中间件
异步Action
异步Action是一种设计模式,不是一种特殊的Action,而是几个Action的组合使用。如上图,当用户在点击View中的一个按钮时,会调用Ajax请求,这是的Action不是单纯的数据结构,而是一个请求,此时Middleware会截获这个请求,分别dispatch一些不同阶段的action。
Middleware
有两种:截获action、发出action
React Router:路由不止是页面切换,更是代码组织方式
-
为什么需要路由?
单页应用需要进行页面切换;
通过URL可以定位到页面;
更有语义地组织资源;
- 三中路由实现方式
URL路由;
hash路由(为了兼容低版本浏览器);
内存路由; - 基于路由配置进行资源组织
实现业务逻辑的松耦合;
易于扩展、重构和维护;
路由层面实现lazy load;
前端项目的理想架构
- 易开发
开发工具是否完善;
生态圈是否繁荣;
社区是否活跃; - 可扩展
增加新功能是否容易;
增加新功能是否会显著增加系统的复杂度; - 易维护
代码是否容易理解;
文档是否健全; - 易测试
功能的分层是否清晰;
副作用少;
尽量使用纯函数; - 易构建
使用通过技术和架构;
构建工具的选择;
拆分项目复杂度:按领域模型(feature)组织代码,降低耦合度
按业务逻辑讲项目拆分成高内聚低耦合的模块;
- 从技术角度划分目录
1.按feature组织components、action和reducer;
2.使用root loader加载每个feature下的各个资源;