MVC
model
用来封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,会有一个或多个视图监听此模型。一旦模型的数据发生变化,模型将通知有关的视图。(有操作数据的方法)
view
视图,当模型发生变化的时候,视图相应得到刷新自己的机会
controller
定义用户界面对用户输入的相应方式,起到不同层面的组织作用,用于控制应用程序的流程,它处理用户行为和数据model
的改变。它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic)。
其中:view
和model
之间是观察者模式,view
观察model
,实现在model
上注册,以便view
可以了解在数据model
上发生的改变view
和controller
是策略模式,view
可以使用controller
的方法
Controller和View都依赖Model层
调用关系:
用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。
需要注意的地方:
- View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
- Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
- View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。
缺点:
- View无法组件化。View是强依赖特定的Model的
MVP
Model View Presenter 为MVC的一种衍生模式
切入点:解决controller
和view
的捆绑关系,将其进行改造,使view
不仅拥有UI组件的结构,还拥有处理用户事件的能力。此时view
不能调用model
的方法,所以只能让presenter
取更新model
,在通过观察者模式更新view
。
相比与传统MVC,解耦了model
和view
,完全分离视图和模型,使职责划分更加清晰。可以将view
抽象出来做成组件。
(controller
可以在view
中复用)。
应用逻辑主要集中在presenter
这一层中。缺点:手动更新
调用关系
和MVC模式一样,用户对View的操作都会从View交移给Presenter。Presenter会执行相应的应用程序逻辑,并且对Model进行相应的操作;而这时候Model执行完业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给Presenter而不是View。Presenter获取到Model变更的消息以后,通过View提供的接口更新界面。
MVVM
最重要的使数据绑定,data-binding。
view和model不知道彼此的存在,同MVP一样,将view和model清晰地分离开,如果viewmodel的属性值改变了,这些新值通过数据绑定自动传递给view,反过来,viewmodel会暴露model中数据和特定状态给view。
此时的model是单纯的数据,不包含对数据的操作
Vue双向数据绑定:通过Object.defineProperty来实现绑定
flux
Flux将一个应用分成四个组成部分:
- View:视图
- Action:一个对象,视图发出的信息,包含一个type以及数据
- Dispatcher:接收Actions,执行回调函数
- Store:数据层,用来存放应用状态,提醒Views更新页面
Predictable
特点:单项数据流动
control view
用来保存状态,监听store的变化,然后数据转发给子组件。
子组件不包含所有的状态,为纯组件。
dispatcher
将action派发到store,dispatcher只有一个,而且是全局的。
Virtual DOM
router control
lifecycle
communication
HOC & mixins
event
React
element & component
element是由component生成的,element是一个对象,component是一个构造函数。
element是immutable(不能改变)的,一旦创建了就不能更改element的children、attributes等属性。
state & props
props只能读,不能更改。所有的React Component 必须像纯函数那样对待他们的props
this.props
与 this.state
可能被异步更新,所以不能依靠他们来计算下一个state
,要使用回调函数:
1 | this.setState((state, props) => ({counter: state.counter + props.counter})) |
dataflow
top-down/ unidirectional 数据流
state只能被特定的组件所拥有,所有的state数据更新只能影响这个组件树下层的组件
Event
React的event使用驼峰写法,而不是全部小写
将一个函数传给下一个组件的事件,而不是字符串
(不能return false来阻止默认行为,只能使用preventDefault来)
React的事件是一个合成事件。所以不需要担心浏览器兼容性
key
注意key相同可能引发错误,使用index作为key也可能引发错误。
以下三个条件满足的情况下中可以使用index作为key:
- list中的数据不会改变(即不会重新计算)
- list不会过滤或者重新保存(如排序)
- list中item没有唯一的id
diff算法·
基于假设:
- 两个不同type的element会生成不同的树
- 可以增加一个key prop来指定哪个element是不变的
virtual-dom
尝试去最小化回流/重绘步骤,从而在大型且复杂的项目中得到更好的性能
virtual-dom ---> fiber
stack-reconciler
fiber
Inside Fiber: in-depth overview of the new reconciliation algorithm in React
the-how-and-why-on-reacts-usage-of-linked-list-in-fiber
Reconciliation
Accessibility
Lazy & Suspense
lazy接收一个含有import
语句的回调,这个回调返回一个promise,(promise中必须有一个包含组件default export)
1 | const Component = React.lazy(() => import('./component')) |
ErrorBoundary
1 | class ErrorBoundary extends Component { |
Error boundaries 不捕获下面的错误
- event handle
- 异步代码
- 服务器端渲染
- Error boundaries 自己抛出的错误
只有class组件才有error boundaries,与catch工作原理类似
在React 16 之后, 如果errors没有被catch,那么会把整个react组件树卸载。
Context
const MyContext = React.createContext(defaultValue)
defaultValue
只有在一个组件上方没有任何的provider
的时候使用。
Context.Provider
1 | <Context.Provider value={/* some value */} /> |
其中上方的value一般不使用字面量对象,一般使用this.state。因为如果是使用字面量对象,每次rerender的时候就会重新创建一个对象
Class.contextType
Context.Consumer
Ref
ref 不是props的属性,如果需要实现穿越组件传递ref,需要使用下面的api
1 | React.forwardRef(function(props, ref) {...}) |
React.forwardRef
接收一个render函数,这个函数接收两个props、ref
使用React.createRef
不能使用在函数组件上,因为函数组件没有实例。
高阶组件
与复合组件区别:复合组件
一般而言,高阶组件是一个接收一个组件并且返回另外一个组件的函数
主要的作用是让不同组件之间共用逻辑
通常,高阶组件是一个纯函数,没有副作用
注意不要更改原组件
注意配置displayName
不要将高阶组件放在render
函数里面,performance以及内部state会消失(React会认为他们是不同的组件)
注意key以及ref的传递
1 | function logProps(WrappedComponent) { |
与mixin区别
mixin是通过使用Object.defineProperty
来实现多个组件之间方法的共用。
问题:
- 破坏了原有组件的封装,不能改变state,或者将state提升的时候很麻烦
- 命名冲突,mixin很难去取出或者移除
- 增加复杂性:增加越来越多的mixin的时候,引入越来越多的方法,造成代码逻辑复杂,不易维护。
性能
- HOC不要在render定义
- 使用data-,代替bind来传参数
- 注意移除event Listener
- 使用
production
版本 - 使用
shouldComponentUpdate
- key
1 | class CounterButton extends React.PureComponent { |
jsx
因为jsx解释出来是这样的:
1 | <div id="2"> |
编译后:
1 | React.createElement('div', |
所以要在作用域内引入React
才能正常工作
1 | // 等价 |
children以及直接使用引号的html是没有转义的,因此可以像写HTML那样使用
false
,null
,undefined
,true
会被忽略
Protals
React可以在render中将element挂载在其他的节点下:
1 | render() { |
使用这个API可以正常使用Context之类的功能,因为它作为一个portal存在于React tree中,(无论它处于哪个DOM tree中)
Hook
1 | import React, { useState } from 'react' |
Hook:
- 很难在组件之间重用一个有状态的逻辑(wrapper hell),不需要改变组件之间的关系重用逻辑
- 复杂组件很难被理解
- 兼容性
state hook
如果在要给render中多次调用useState,React会以相同的顺序调用他们
Hook就是一个钩子能够让你hook进去React函数组件的state和lifecycle(Hook不能在class中工作)
effect hook
获取数据,订阅,或者修改DOM这种副作用的时候使用
React默认在render之后执行effects(包括第一次render)
useEffect可以return一个函数作为callback
这个方法可以告诉React在render完成之后应该干什么,返回的函数称为clearup,React在组件unmount的时候运行这个clearup函数(React都会运行effect之前的clearup函数)==》原因:
如果props改变了,那么就可能会出现bug
可以告诉React跳过这个effect,使用第二个参数(数组)。
1 | useEffext(() => document.title = `click ${count} times`, [count]) |
rules
- 不要再循环、条件或者嵌套函数中使用hook
- 只在React函数组件中使用
React怎么识别出哪个state是哪个useState调用的?
React根据hook被调用的顺序来判断的。(因为每次render的时候他们的顺序都不会发生改变)
前端路由的实现
- hash模式,通过改变hash,监听hashchange事件来进行页面跳转
- history模式,通过使用history中的新功能:history.putState和history.replaceState来改变URL(通过History模式改变URL不会引起页面的刷新,只会更新浏览器的历史纪录。)如果用户点击后退按钮,会触发popState事件
Vue 和 React 的区别
- Vue修改状态要简单一点,React需要使用setState来改变状态,并且需要手动优化
- React使用了JSX,完全可以通过JS来控制页面,更加的灵活。Vue使用了模板语法,相比于JSX来说没有那么灵活,但是可以脱离工具链,通过直接编写render函数就能够在浏览器中运行。