React16
经常使用api
解析以及原理剖析Vue
与 React
两个框架的粗略区别对比react 16
版本常见 api
react
生命周期react
事件机制react.Component
如何实现组件化以及高阶组件的应用setState
异步队列数据管理react Fiber
架构分析react hooks
dom
的 diff
算法snabbdom
源码,是怎样实现精简的 Virtual DOM
的redux
单向数据流架构如何设计Vue
与 React
两个框架的粗略区别对比Vue 的优点包括:css
React 的优点包括:html
React 与 Vue 有不少类似之处,React 和 Vue 都是很是优秀的框架,它们之间的类似之处多过不一样之处,而且它们大部分最棒的功能是相通的:如他们都是 JavaScript 的 UI 框架,专一于创造前端的富应用。不一样于早期的 JavaScript 框架“功能齐全”,Reat 与 Vue 只有框架的骨架,其余的功能如路由、状态管理等是框架分离的组件。前端
从二者的 github 表现来看(数据取于 2019-09-16)vue
能够看出 vue 的 star 数量已是前端框架中最火爆的。从维护上来看,react 是 facebook 在维护,而 vue 现阶段虽然也有了团队,但主要仍是尤雨溪在维护贡献代码,而且阿里巴巴开源的混合式框架 weex 也是基于 vue 的,因此咱们相信 vue 将来将会获得更多的人和团队维护。node
根据不彻底统计,包括饿了么、简书、高德、稀土掘金、苏宁易购、美团、天猫、荔枝 FM、房多多、Laravel、htmlBurger 等国内外知名大公司都在使用 vue 进行新项目的开发和旧项目的前端重构工做。react
使用 React 的公司 facebook、Twitter、INS、Airbnb、Yahoo、ThoughtWorks、蚂蚁金服、阿里巴巴、腾讯、百度、口碑、美团、滴滴出行、饿了么、京东、网易等。webpack
vue | react | |
---|---|---|
pc 端 | iview、element 等 | Ant Design、Materal-UI 等 |
h5 端 | 有赞 vant、mintui 等 | Ant Design Mobile、weui |
混合开发 | weexui、bui-weex | teaset、react-native-elements |
微信小程序 | iview、Weapp、zanui | iView Weapp、Taro UI |
不管您选择React.js仍是Vue.js,两个框架都没有至关大的差别,根据您的要求,这个决定是很是主观的。若是您想将前端JavaScript框架集成到现有应用程序中,Vue.js是更好的选择,若是您想使用JavaScript构建移动应用程序,React绝对是您的选择。git
react16
版本常见 api
先来看一下 react 暴露出来的 APIgithub
const React = {
Children: {
map,
forEach,
count,
toArray,
only
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals
}
复制代码
Children
这个对象提供了一堆帮你处理 props.children 的方法,由于 children 是一个相似数组可是不是数组的数据结构,若是你要对其进行处理能够用 React.Children 外挂的方法。web
createRef
新的 ref 用法,React 即将抛弃<div ref="myDiv" />
这种 string ref 的用法,未来你只能使用两种方式来使用 ref
class App extends React.Component {
constructor() {
this.ref = React.createRef()
}
render() {
return <div ref={this.ref} />
// or
return <div ref={node => (this.funRef = node)} />
}
}
复制代码
createContext
createContext
是官方定稿的 context 方案,在这以前咱们一直在用的老的 context API 都是 React 不推荐的 API,如今新的 API 释出,官方也已经肯定在 17 大版本会把老 API 去除(老 API 的性能不是通常的差)。
新 API 的使用方法:
const { Provider, Consumer } = React.createContext('defaultValue')
const ProviderComp = (props) => (
<Provider value={'realValue'}> {props.children} </Provider>
)
const ConsumerComp = () => (
<Consumer> {(value) => <p>{value}</p>} </Consumber>
)
复制代码
目前 react 16.8 +的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段
constructor(props)
: 实例化。static getDerivedStateFromProps
从 props
中获取 state
。render
渲染。componentDidMount
: 完成挂载。static getDerivedStateFromProps
从 props 中获取 state。shouldComponentUpdate
判断是否须要重绘。render
渲染。getSnapshotBeforeUpdate
获取快照。componentDidUpdate
渲染完成后回调。componentWillUnmount
即将卸载。static getDerivedStateFromError
从错误中获取 state
。componentDidCatch
捕获错误并进行处理。class ExampleComponent extends react.Component {
// 构造函数,最早被执行,咱们一般在构造函数里初始化state对象或者给自定义方法绑定this
constructor() {}
//getDerivedStateFromProps(nextProps, prevState)用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
// 这是个静态方法,当咱们接收到新的属性想去修改咱们state,可使用getDerivedStateFromProps
static getDerivedStateFromProps(nextProps, prevState) {
// 新的钩子 getDerivedStateFromProps() 更加纯粹, 它作的事情是将新传进来的属性和当前的状态值进行对比, 若不一致则更新当前的状态。
if (nextProps.riderId !== prevState.riderId) {
return {
riderId: nextProps.riderId
}
}
// 返回 null 则表示 state 不用做更新
return null
}
// shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化以后的state,返回一个布尔值,true表示会触发从新渲染,false表示不会触发从新渲染,默认返回true,咱们一般利用今生命周期来优化react程序性能
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id
}
// 组件挂载后调用
// 能够在该函数中进行请求或者订阅
componentDidMount() {}
// getSnapshotBeforeUpdate(prevProps, prevState):这个方法在render以后,componentDidUpdate以前调用,有两个参数prevProps和prevState,表示以前的属性和以前的state,这个函数有一个返回值,会做为第三个参数传给componentDidUpdate,若是你不想要返回值,能够返回null,今生命周期必须与componentDidUpdate搭配使用
getSnapshotBeforeUpdate() {}
// 组件即将销毁
// 能够在此处移除订阅,定时器等等
componentWillUnmount() {}
// 组件销毁后调用
componentDidUnMount() {}
// componentDidUpdate(prevProps, prevState, snapshot):该方法在getSnapshotBeforeUpdate方法以后被调用,有三个参数prevProps,prevState,snapshot,表示以前的props,以前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,若是触发某些回调函数时须要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,而后在 componentDidUpdate 中统一触发回调或更新状态。
componentDidUpdate() {}
// 渲染组件函数
render() {}
// 如下函数不建议使用
UNSAFE_componentWillMount() {}
UNSAFE_componentWillUpdate(nextProps, nextState) {}
UNSAFE_componentWillReceiveProps(nextProps) {}
}
复制代码
react 版本 17 将弃用几个类组件 API 生命周期:componentWillMount
,componentWillReceiveProps
和componentWillUpdate
。
class Button extends react.Component {
constructor(props) {
super(props)
this.handleClick1 = this.handleClick1.bind(this)
}
//方式1:在构造函数中使用bind绑定this,官方推荐的绑定方式,也是性能最好的方式
handleClick1() {
console.log('this is:', this)
}
//方式2:在调用的时候使用bind绑定this
handleClick2() {
console.log('this is:', this)
}
//方式3:在调用的时候使用箭头函数绑定this
// 方式2和方式3会有性能影响而且当方法做为属性传递给子组件的时候会引发重渲问题
handleClick3() {
console.log('this is:', this)
}
//方式4:使用属性初始化器语法绑定this,须要babel转义
handleClick4 = () => {
console.log('this is:', this)
}
render() {
return (
<div> <button onClick={this.handleClick1}>Click me</button> <button onClick={this.handleClick2.bind(this)}>Click me</button> <button onClick={() => this.handleClick3}>Click me</button> <button onClick={this.handleClick4}>Click me</button> </div>
)
}
}
复制代码
为何直接调用方法会报错
class Foo extends React.Component {
handleClick() {
this.setState({ xxx: aaa })
}
render() {
return <button onClick={this.handleClick.bind(this)}>Click me</button>
}
}
复制代码
会被 babel 转化成
React.createElement(
'button',
{
onClick: this.handleClick
},
'Click me'
)
复制代码
react 实现了一个“合成事件”层(synthetic event system
),这抹平了各个浏览器的事件兼容性问题。全部事件均注册到了元素的最顶层-document 上,“合成事件”会以事件委托(event delegation
)的方式绑定到组件最上层,而且在组件卸载(unmount
)的时候自动销毁绑定的事件。
import classNames from 'classnames'
class Button extends react.Component {
//参数传参与校验
static propTypes = {
type: PropTypes.oneOf(['success', 'normal']),
onClick: PropTypes.func
}
static defaultProps = {
type: 'normal'
}
handleClick() {}
render() {
let { className, type, children, ...other } = this.props
const classes = classNames(
className,
'prefix-button',
'prefix-button-' + type
)
return (
<span className={classes} {...other} onClick={() => this.handleClick}> {children} </span>
)
}
}
复制代码
纯展现型的,不须要维护 state 和生命周期,则优先使用 Function Component
import react from 'react'
function MyComponent(props) {
let { firstName, lastName } = props
return (
<div> <img src="avatar.png" className="profile" /> <h3>{[firstName, lastName].join(' ')}</h3> </div> ) } 复制代码
会被 babel 转义成
return React.createElement(
'div',
null,
React.createElement('img', { src: 'avatar.png', className: 'profile' }),
React.createElement('h3', null, [firstName, lastName].join(' '))
)
复制代码
那么,React.createElement
是在作什么?看下相关部分代码:
var ReactElement = function(type, key, ref, self, source, owner, props) {
var element = {
// This tag allow us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
}
// ...
return element
}
ReactElement.createElement = function(type, config, children) {
// ...
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props
)
}
复制代码
React.createElement()来构建 React 元素的。它接受三个参数,第一个参数type能够是一个标签名。如 div、span,或者 React 组件。第二个参数props为传入的属性。第三个以及以后的参数children,皆做为组件的子组件。
createElement
函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps
对默认 props 进行赋值,而且对传入的孩子节点进行处理,最终构形成一个 reactElement
对象(所谓的虚拟 DOM)。 reactDOM.render
将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制而且对特定浏览器进行了性能优化,最终转换为真实 DOM。
ES6 class
定义一个纯组件(PureComponent
)组件须要维护 state 或使用生命周期方法,则优先使用 PureComponent
class MyComponent extends react.Component {
render() {
let { name } = this.props
return <h1>Hello, {name}</h1>
}
}
复制代码
PureComponent
Component
& PureComponent
这两个类基本相同,惟一的区别是 PureComponent
的原型上多了一个标识,shallowEqual
(浅比较),来决定是否更新组件,浅比较相似于浅复制,只会比较第一层。使用 PureComponent
至关于省去了写 shouldComponentUpdate
函数
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
}
复制代码
这是检查组件是否须要更新的一个判断,ctor 就是你声明的继承自 Component
or PureComponent
的类,他会判断你是否继承自 PureComponent,若是是的话就
shallowEqual
比较 state 和 props。
React 中对比一个 ClassComponent
是否须要更新,只有两个地方。一是看有没有 shouldComponentUpdate
方法,二就是这里的 PureComponent
判断
Immutablejs
Immutable.js
是 Facebook 在 2014 年出的持久性数据结构的库,持久性指的是数据一旦建立,就不能再被更改,任何修改或添加删除操做都会返回一个新的 Immutable
对象。可让咱们更容易的去处理缓存、回退、数据变化检测等问题,简化开发。而且提供了大量的相似原生 JS 的方法,还有 Lazy Operation
的特性,彻底的函数式编程。
import { Map } from 'immutable'
const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 })
const map2 = map1.set('b', 50)
map1 !== map2 // true
map1.get('b') // 2
map2.get('b') // 50
map1.get('a') === map2.get('a') // true
复制代码
能够看到,修改 map1 的属性返回 map2,他们并非指向同一存储空间,map1 声明了只有,全部的操做都不会改变它。
ImmutableJS
提供了大量的方法去更新、删除、添加数据,极大的方便了咱们操纵数据。除此以外,还提供了原生类型与 ImmutableJS
类型判断与转换方法:
import { fromJS, isImmutable } from 'immutable'
const obj = fromJS({
a: 'test',
b: [1, 2, 4]
}) // 支持混合类型
isImmutable(obj) // true
obj.size() // 2
const obj1 = obj.toJS() // 转换成原生 `js` 类型
复制代码
ImmutableJS
最大的两个特性就是: immutable data structures
(持久性数据结构)与 structural sharing
(结构共享),持久性数据结构保证数据一旦建立就不能修改,使用旧数据建立新数据时,旧数据也不会改变,不会像原生 js 那样新数据的操做会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减小了深拷贝的性能消耗,也减小了内存。
左边是旧值,右边是新值,我须要改变左边红色节点的值,生成的新值改变了红色节点到根节点路径之间的全部节点,也就是全部青色节点的值,旧值没有任何改变,其余使用它的地方并不会受影响,而超过一大半的蓝色节点仍是和旧值共享的。在 ImmutableJS
内部,构造了一种特殊的数据结构,把原生的值结合一系列的私有属性,建立成 ImmutableJS
类型,每次改变值,先会经过私有属性的辅助检测,而后改变对应的须要改变的私有属性和真实值,最后生成一个新的值,中间会有不少的优化,因此性能会很高。
higher order component
)高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props
if (visible === false) return null
return <WrappedComponent {...props} /> } } } 复制代码
上面的代码就是一个 HOC 的简单应用,函数接收一个组件做为参数,并返回一个新组件,新组建能够接收一个 visible props,根据 visible 的值来判断是否渲染 Visible。 最多见的还有 Redux 的 connect 函数。除了简单分享工具库和简单的组合,HOC 最好的方式是共享 react 组件之间的行为。若是你发现你在不一样的地方写了大量代码来作同一件事时,就应该考虑将代码重构为可重用的 HOC。 下面就是一个简化版的 connect 实现:
export const connect = (
mapStateToProps,
mapDispatchToProps
) => WrappedComponent => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor() {
super()
this.state = {
allProps: {}
}
}
componentWillMount() {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps() {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {}
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {}
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render() {
return <WrappedComponent {...this.state.allProps} /> } } return Connect } 复制代码
代码很是清晰,connect 函数其实就作了一件事,将 mapStateToProps
和 mapDispatchToProps
分别解构后传给原组件,这样咱们在原组件内就能够直接用 props
获取 state
以及 dispatch
函数了。
某些页面须要记录用户行为,性能指标等等,经过高阶组件作这些事情能够省去不少重复代码。
function logHoc(WrappedComponent) {
return class extends Component {
componentWillMount() {
this.start = Date.now()
}
componentDidMount() {
this.end = Date.now()
console.log(
`${WrappedComponent.dispalyName} 渲染时间:${this.end - this.start} ms`
)
console.log(`${user}进入${WrappedComponent.dispalyName}`)
}
componentWillUnmount() {
console.log(`${user}退出${WrappedComponent.dispalyName}`)
}
render() {
return <WrappedComponent {...this.props} /> } } } 复制代码
function auth(WrappedComponent) {
return class extends Component {
render() {
const { visible, auth, display = null, ...props } = this.props
if (visible === false || (auth && authList.indexOf(auth) === -1)) {
return display
}
return <WrappedComponent {...props} /> } } } 复制代码
基于上面的双向绑定的例子,咱们再来一个表单验证器,表单验证器能够包含验证函数以及提示信息,当验证不经过时,展现错误信息:
function validateHoc(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props)
this.state = { error: '' }
}
onChange = event => {
const { validator } = this.props
if (validator && typeof validator.func === 'function') {
if (!validator.func(event.target.value)) {
this.setState({ error: validator.msg })
} else {
this.setState({ error: '' })
}
}
}
render() {
return (
<div> <WrappedComponent onChange={this.onChange} {...this.props} /> <div>{this.state.error || ''}</div> </div> ) } } } 复制代码
const validatorName = {
func: (val) => val && !isNaN(val),
msg: '请输入数字'
}
const validatorPwd = {
func: (val) => val && val.length > 6,
msg: '密码必须大于6位'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>
复制代码
render props
一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术 具备 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现本身的渲染逻辑。
<DataProvider render={data => <h1>Hello {data.target}</h1>} />
复制代码
setState
数据管理// Wrong 此代码不会从新渲染组件,构造函数是惟一可以初始化 this.state 的地方。
this.state.comment = 'Hello'
// Correct 应当使用 setState():
this.setState({ comment: 'Hello' })
复制代码
组件生命周期中或者 react 事件绑定中,setState 是经过异步更新的,在延时的回调或者原生事件绑定的回调中调用 setState 不必定是异步的。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment
})
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}))
复制代码
原生事件绑定不会经过合成事件的方式处理,会进入更新事务的处理流程。`setTimeout` 也同样,在 `setTimeout` 回调执行时已经完成了原更新组件流程,不会放入 `dirtyComponent` 进行异步更新,其结果天然是同步的。
setState
原理setState 并无直接操做去渲染,而是执行了一个 updateQueue
(异步 updater 队列),
setState( stateChange ) {
Object.assign( this.state, stateChange );
//合并接收到的state||stateChange改变的state(setState接收到的参数)
renderComponent( this );//调用render渲染组件
}
复制代码
这种实现,每次调用 setState
都会更新 state 并立刻渲染一次(不符合其更新优化机制),因此咱们要合并 setState
。
具体能够阅读源码 ReactUpdateQueue.js
react
中的事务实现待完善 这块看的还有点懵圈 React 源码解析(三):详解事务与更新队列 React 中的 Transaction React 的事务机制
ErrorBoundary
、Suspense
和 Fragment
Error Boundaries
react 16 提供了一个新的错误捕获钩子 componentDidCatch(error, errorInfo)
, 它能将子组件生命周期里所抛出的错误捕获, 防止页面全局崩溃。demo componentDidCatch
并不会捕获如下几种错误
lazy、Suspence
延迟加载组件lazy
须要跟 Suspence
配合使用,不然会报错。
lazy
其实是帮助咱们实现代码分割 ,相似 webpack 的 splitchunk
的功能。
Suspense
意思是能暂停当前组件的渲染, 当完成某件事之后再继续渲染。简单来讲就是减小首屏代码的体积,提高性能。
import react, { lazy, Suspense } from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
return (
<Suspense fallback={<div>loading...</div>}> <OtherComponent /> </Suspense>
)
}
复制代码
一种简单的预加载思路, 可参考 preload
const OtherComponentPromise = import('./OtherComponent')
const OtherComponent = react.lazy(() => OtherComponentPromise)
复制代码
Fragments(v16.2.0)
Fragments 容许你将子列表分组,避免向 DOM 添加额外的节点。
render() {
return (
<> <ChildA /> <ChildB /> <ChildC /> </> ); } 复制代码
react Fiber
架构分析react-fiber
是为了加强动画、布局、移动端手势领域的适用性,最重要的特性是对页面渲染的优化: 容许将渲染方面的工做拆分为多段进行。
react Fiber
架构解决了什么问题react-fiber
能够为咱们提供以下几个功能:
Fiber
如何作到异步渲染 Virtual Dom
和 Diff
算法众所周知,画面每秒钟更新 60 次,页面在人眼中显得流畅,无明显卡顿。每秒 60 次,即 16ms 要更新一次页面,若是更新页面消耗的时间不到 16ms,那么在下一次更新时机来到以前会剩下一点时间执行其余的任务,只要保证及时在 16ms 的间隔下更新界面就彻底不会影响到页面的流畅程度。fiber 的核心正是利用了 60 帧原则,实现了一个基于优先级和 requestIdleCallback 的循环任务调度算法。
function fiber(剩余时间) {
if (剩余时间 > 任务所需时间) {
作任务
} else {
requestIdleCallback(fiber)
// requestIdleCallback 是浏览器提供的一个 api,可让浏览器在空闲的时候执行回调,
// 在回调参数中能够获取到当前帧剩余的时间,fiber 利用了这个参数,
// 判断当前剩下的时间是否足够继续执行任务,
// 若是足够则继续执行,不然暂停任务,
// 并调用 requestIdleCallback 通知浏览器空闲的时候继续执行当前的任务
}
}
复制代码
react hooks
在 react 16.7 以前, react 有两种形式的组件, 有状态组件(类)和无状态组件(函数)。 官方解释: hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。 我的理解:让传统的函数组件 function component 有内部状态 state 的函数 function,简单来讲就是 hooks 让函数组件有了状态,能够彻底替代 class。
接下来梳理 Hooks 中最核心的 2 个 api, useState
和 useEffect
useState
useState 是一个钩子,他能够为函数式组件增长一些状态,而且提供改变这些状态的函数,同时它接收一个参数,这个参数做为状态的默认值。
const [count, setCount] = useState(initialState)
复制代码
使用 Hooks 相比以前用 class 的写法最直观的感觉是更为简洁
function App() {
const [count, setCount] = useState(0)
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div>
)
}
复制代码
useEffect(fn)
在每次 render 后都会执行这个钩子。能够将它当成是 componentDidMount
、componentDidUpdate``、componentWillUnmount
的合集。所以使用 useEffect
比以前优越的地方在于:
能够避免在 componentDidMount
、componentDidUpdate
书写重复的代码; 能够将关联逻辑写进一个 useEffect
(在之前得写进不一样生命周期里);
使用 react.createElement
或 JSX 编写 react 组件,实际上全部的 JSX 代码最后都会转换成 react.createElement(...)
,Babel 帮助咱们完成了这个转换的过程。
createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps
对默认 props 进行赋值,而且对传入的孩子节点进行处理,最终构形成一个 reactElement
对象(所谓的虚拟 DOM)。
reactDOM.render
将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制而且对特定浏览器进行了性能优化,最终转换为真实 DOM。
即 reactElementelement
对象,咱们的组件最终会被渲染成下面的结构:
`type`:元素的类型,能够是原生 html 类型(字符串),或者自定义组件(函数或 class)
`key`:组件的惟一标识,用于 Diff 算法,下面会详细介绍
`ref`:用于访问原生 dom 节点
`props`:传入组件的 props,chidren 是 props 中的一个属性,它存储了当前组件的孩子节点,能够是数组(多个孩子节点)或对象(只有一个孩子节点)
`owner`:当前正在构建的 Component 所属的 Component
`self`:(非生产环境)指定当前位于哪一个组件实例
`_source`:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)
复制代码
当组件状态 state 有更改的时候,react 会自动调用组件的 render 方法从新渲染整个组件的 UI。 固然若是真的这样大面积的操做 DOM,性能会是一个很大的问题,因此 react 实现了一个 Virtual DOM
,组件 DOM 结构就是映射到这个 Virtual DOM
上,react 在这个 Virtual DOM
上实现了一个 diff 算法,当要从新渲染组件的时候,会经过 diff 寻找到要变动的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,因此实际上不是真的渲染整个 DOM 树。这个 Virtual DOM
是一个纯粹的 JS 数据结构,因此性能会比原生 DOM 快不少。
react
是如何防止 XSS
的reactElement
对象还有一个$$typeof
属性,它是一个 Symbol 类型的变量Symbol.for('react.element')
,当环境不支持 Symbol 时,$$typeof
被赋值为 0xeac7
。 这个变量能够防止 XSS。若是你的服务器有一个漏洞,容许用户存储任意 JSON 对象, 而客户端代码须要一个字符串,这可能为你的应用程序带来风险。JSON 中不能存储 Symbol
类型的变量,而 react 渲染时会把没有\$\$typeof
标识的组件过滤掉。
diff
算法传统的 diff
算法经过循环递归对节点一次对比,效率很低,算法复杂度达到 O(n^3),其中 n 是树中节点的总数,React 经过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。
diff
策略:
diff
算法比较新旧节点的时候,比较只会在同层级比较,不会跨层级比较基于以上三个前提策略,React 分别对 tree diff
、component diff
以及 element diff
进行算法优化,事实也证实这三个前提策略是合理且准确的,它保证了总体界面构建的性能。 简单的讲就是:
具体能够参考React 源码剖析系列 - 难以想象的 react diff
tree diff
进行算法优化;component diff
进行算法优化;element diff
进行算法优化;建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提高; 建议,在开发过程当中,尽可能减小相似将最后一个节点移动到列表首部的操做,当节点数量过大或更新操做过于频繁时,在必定程度上会影响 React 的渲染性能。
待补充
在使用 class Component
进行开发的时候,咱们可使用 shouldComponentUpdate
来减小没必要要的渲染,那么在使用 react hooks
后,咱们如何实现这样的功能呢?
解决方案:React.memo
和useMemo
对于这种状况,react 固然也给出了官方的解决方案,就是使用 React.memo 和 useMemo。
React.memo
React.momo 其实并非一个 hook,它其实等价于 PureComponent,可是它只会对比 props。使用方式以下(用上面的例子):
import React, { useState } from 'react'
export const Count = React.memo(props => {
const [data, setData] = useState({
count: 0,
name: 'cjg',
age: 18
})
const handleClick = () => {
const { count } = data
setData({
...data,
count: count + 1
})
}
return <button onClick={handleClick}>count:{data.count}</button>
})
复制代码
useMemo 它的用法其实跟 useEffects 有点像,咱们直接看官方给的例子
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a])
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b])
return (
<> {child1} {child2} </> ) } 复制代码
从例子能够看出来,它的第二个参数和 useEffect 的第二个参数是同样的,只有在第二个参数数组的值发生变化时,才会触发子组件的更新。
当一个组件的 props 或 state 变动,React 会将最新返回的元素与以前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。
即便 React 只更新改变了的 DOM 节点,从新渲染仍然花费了一些时间。在大部分状况下它并非问题,可是若是渲染的组件很是多时,就会浮现性能上的问题,咱们能够经过覆盖生命周期方法 shouldComponentUpdate 来进行提速。
shouldComponentUpdate 方法会在从新渲染前被触发。其默认实现老是返回 true,若是组件不须要更新,能够在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及以后的操做。
shouldComponentUpdate(nextProps, nextState) {
return nextProps.next !== this.props.next
}
复制代码
React 16.5 增长了对新的开发者工具 DevTools 性能分析插件的支持。 此插件使用 React 实验性的 Profiler API 来收集有关每一个组件渲染的用时信息,以便识别 React 应用程序中的性能瓶颈。 它将与咱们即将推出的 time slicing(时间分片) 和 suspense(悬停) 功能彻底兼容。
Store
:保存数据的地方,你能够把它当作一个容器,整个应用只能有一个 Store
。
State
:Store
对象包含全部数据,若是想获得某个时点的数据,就要对 Store
生成快照,这种时点的数据集合,就叫作 State
。
Action
:State
的变化,会致使 View 的变化。可是,用户接触不到 State,只能接触到 View。因此,State 的变化必须是 View 致使的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action Creator
:View 要发送多少种消息,就会有多少种 Action
。若是都手写,会很麻烦,因此咱们定义一个函数来生成 Action,这个函数就叫 Action Creator
。
Reducer
:Store
收到 Action
之后,必须给出一个新的 State
,这样 View 才会发生变化。这种 State
的计算过程就叫作 Reducer
。Reducer
是一个函数,它接受 Action 和当前 State
做为参数,返回一个新的 State
。
dispatch
:是 View
发出 Action
的惟一方法。
而后咱们过下整个工做流程:
首先,用户(经过 View
)发出 Action
,发出方式就用到了 dispatch
方法。
而后,Store
自动调用 Reducer
,而且传入两个参数:当前 State
和收到的 Action
,Reducer
会返回新的 State
State
一旦有变化,Store
就会调用监听函数,来更新 View
。
到这儿为止,一次用户交互流程结束。能够看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。
redux
单向数据流架构如何设计待完善
redux
中间件Redux 的中间件提供的是位于 action 被发起以后,到达 reducer 以前的扩展点,换而言之,本来 view -> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,在这一环节咱们能够作一些 “反作用” 的操做,如 异步请求、打印日志等。
redux 中间件经过改写 store.dispatch 方法实现了 action -> reducer 的拦截,从上面的描述中能够更加清晰地理解 redux 中间件的洋葱圈模型:
中间件A -> 中间件B-> 中间件C-> 原始 dispatch -> 中间件C -> 中间件B -> 中间件A
复制代码
这也就提醒咱们使用中间件时须要注意这个中间件是在何时 “搞事情” 的,好比 redux-thunk 在执行 next(action) 前就拦截了类型为 function 的 action,而 redux-saga 就在 next(action) 才会触发监听 sagaEmitter.emit(action), 并不会拦截已有 action 到达 reducer。