react

文章目录

MVC

MVC

MV*

model 用来封装与应用程序的业务逻辑相关的数据以及对数据的处理方法,会有一个或多个视图监听此模型。一旦模型的数据发生变化,模型将通知有关的视图。(有操作数据的方法)

view 视图,当模型发生变化的时候,视图相应得到刷新自己的机会

controller 定义用户界面对用户输入的相应方式,起到不同层面的组织作用,用于控制应用程序的流程,它处理用户行为和数据model的改变。它的职责为进行Model和View之间的协作(路由、输入预处理等)的应用逻辑(application logic)。

其中:viewmodel之间是观察者模式,view观察model,实现在model上注册,以便view可以了解在数据model上发生的改变
viewcontroller是策略模式,view可以使用controller的方法

Controller和View都依赖Model层

调用关系:

用户的对View操作以后,View捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller会对来自View数据进行预处理、决定调用哪个Model的接口;然后由Model执行相关的业务逻辑;当Model变更了以后,会通过观察者模式(Observer Pattern)通知View;View通过观察者模式收到Model变更的消息以后,会向Model请求最新的数据,然后重新更新界面。

需要注意的地方:

  1. View是把控制权交移给Controller,Controller执行应用程序相关的应用逻辑(对来自View数据进行预处理、决定调用哪个Model的接口等等)。
  2. Controller操作Model,Model执行业务逻辑对数据进行处理。但不会直接操作View,可以说它是对View无知的。
  3. View和Model的同步消息是通过观察者模式进行,而同步操作是由View自己请求Model的数据然后对视图进行更新。

缺点:

  1. View无法组件化。View是强依赖特定的Model的

MVP

Model View Presenter 为MVC的一种衍生模式

切入点:解决controllerview的捆绑关系,将其进行改造,使view不仅拥有UI组件的结构,还拥有处理用户事件的能力。此时view不能调用model的方法,所以只能让presenter取更新model,在通过观察者模式更新view
相比与传统MVC,解耦了modelview,完全分离视图和模型,使职责划分更加清晰。可以将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来实现绑定

vue的双向数据绑定

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.propsthis.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

react-key

注意key相同可能引发错误,使用index作为key也可能引发错误。

以下三个条件满足的情况下中可以使用index作为key:

  • list中的数据不会改变(即不会重新计算)
  • list不会过滤或者重新保存(如排序)
  • list中item没有唯一的id

diff算法·

diff-algorithm

基于假设:

  1. 两个不同type的element会生成不同的树
  2. 可以增加一个key prop来指定哪个element是不变的

virtual-dom

尝试去最小化回流/重绘步骤,从而在大型且复杂的项目中得到更好的性能

virtual-dom ---> fiber

virtual-dom

stack-reconciler

fiber

完全理解React Fiber

React 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

checklist

Lazy & Suspense

lazy接收一个含有import语句的回调,这个回调返回一个promise,(promise中必须有一个包含组件default export)

1
2
3
4
5
6
7
8
9
const Component = React.lazy(() => import('./component'))

function MyComponent() {
return (
<Suspense fallback={<div>loading</div>}>
<Component />
</Suspense>
)
}

ErrorBoundary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = {
error: false,
msg: ''
}
}

static getDerivedStateFromError(err) {
return {
error: true,
msg: error.message
}
}

componentDidCatch() {

}

render() {
const { error, msg } = this.state
return (
<>
{error ? <div>error: {msg}</div> : this.props.children}
</>
)
}
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
React.forwardRef(function(props, ref) {...})

function logProps(Component) {
class logProps extends React.Component {
render() {
const {forwardedRef, ...rest} = this.props
return <Component ref={forwardedRef} {...rest} />
}
}

return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />
})
}

React.forwardRef接收一个render函数,这个函数接收两个props、ref

使用React.createRef不能使用在函数组件上,因为函数组件没有实例。

高阶组件

与复合组件区别:复合组件

一般而言,高阶组件是一个接收一个组件并且返回另外一个组件的函数

主要的作用是让不同组件之间共用逻辑

通常,高阶组件是一个纯函数,没有副作用

注意不要更改原组件

注意配置displayName

不要将高阶组件放在render函数里面,performance以及内部state会消失(React会认为他们是不同的组件)

注意key以及ref的传递

1
2
3
4
5
6
7
8
9
10
11
function logProps(WrappedComponent) {
return class extends React.Component {
//... some method
render () {
const { extraProps, ...passThroughProps } = this.props
return (
<WrappedComponent injectProp={injectProp} {...passThroughProps} /> //传递props以及插入props
)
}
}
}

与mixin区别

mixins-considered-harmful

mixin是通过使用Object.defineProperty来实现多个组件之间方法的共用。
问题:

  1. 破坏了原有组件的封装,不能改变state,或者将state提升的时候很麻烦
  2. 命名冲突,mixin很难去取出或者移除
  3. 增加复杂性:增加越来越多的mixin的时候,引入越来越多的方法,造成代码逻辑复杂,不易维护。

性能

  1. HOC不要在render定义
  2. 使用data-,代替bind来传参数
  3. 注意移除event Listener
  4. 使用production版本
  5. 使用shouldComponentUpdate
  6. key
1
2
3
class CounterButton extends React.PureComponent {
// PureComponent封装了shouldComponentUpdate所需要的逻辑,对props和state进行浅比较
}

jsx

因为jsx解释出来是这样的:

1
2
3
4
5
6
<div id="2">
233
{value}
<Foo />
<></>
</div>

编译后:

1
2
3
4
5
React.createElement('div', 
{id: '2'},
'233',
value,
React.createElement(Foo, null), React.createElement(React.Fragment, null))

所以要在作用域内引入React才能正常工作

1
2
3
// 等价
<MyComponent message="&lt;3" />
<MyComponent message={'<3'} />

children以及直接使用引号的html是没有转义的,因此可以像写HTML那样使用

falsenullundefinedtrue会被忽略

Protals

React可以在render中将element挂载在其他的节点下:

1
2
3
render() {
return ReactDOM.createPortal(this.props.children, domNode)
}

使用这个API可以正常使用Context之类的功能,因为它作为一个portal存在于React tree中,(无论它处于哪个DOM tree中)

Hook

1
2
3
4
5
6
7
8
import React, { useState } from 'react'

function Test() {
const [count, setCount] = useState(0)
return (
<div>{count}</div>
)
}

Hook:

  1. 很难在组件之间重用一个有状态的逻辑(wrapper hell),不需要改变组件之间的关系重用逻辑
  2. 复杂组件很难被理解
  3. 兼容性

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
2
useEffext(() => document.title = `click ${count} times`, [count])
useEffect(() => {/*do something*/}, []) //空数组说明这个sideEffect不依靠于state,所以不需要在re-render的时候更新

rules

  1. 不要再循环、条件或者嵌套函数中使用hook
  2. 只在React函数组件中使用

React怎么识别出哪个state是哪个useState调用的?

React根据hook被调用的顺序来判断的。(因为每次render的时候他们的顺序都不会发生改变)

前端路由的实现

  1. hash模式,通过改变hash,监听hashchange事件来进行页面跳转
  2. history模式,通过使用history中的新功能:history.putState和history.replaceState来改变URL(通过History模式改变URL不会引起页面的刷新,只会更新浏览器的历史纪录。)如果用户点击后退按钮,会触发popState事件

Vue 和 React 的区别

  1. Vue修改状态要简单一点,React需要使用setState来改变状态,并且需要手动优化
  2. React使用了JSX,完全可以通过JS来控制页面,更加的灵活。Vue使用了模板语法,相比于JSX来说没有那么灵活,但是可以脱离工具链,通过直接编写render函数就能够在浏览器中运行。
分享到: