React 主要内容

499 阅读25分钟

1、jsx 基本使用

  • 变量、表达式
  • class style
  • 子元素和组件

2、循环、判断

不解释

3、事件

  • bind this
  • 关于 event 参数
  • 传递自定义参数

1)、this的绑定

改变 this 的指向

  1. 初始化 bind 的方式 ( 只在初始化 bind )
  2. 在定义方法的时候 bind 。 不建议使用 (多次bind)
  3. 通过箭头函数的形式。
  4. 静态方法 this 永远是 当前实例。(推荐使用)

2)、关于 event 参数

  • event.preventDefault() 阻止默认行为
  • event.stopPropagetion() 阻止冒泡
  • event.target 指向当前元素,即当前元素触发
  • event.currentTaget 指向当前元素 假象!!!

react 中的 event 是 react 自己封装的, 不是原生的 event 。可以看 proto.constructor 是 SyntheticEvent (组合事件),可以通过 event.nativeEvent 获取原生 event 。原生 event proto.constructor 是 MouseEvent 。

  1. event 是 SyntheticEvent (合成事件) 模拟出 DOM 事件的所有能力
  2. event.nativeEvent 是原生事件
  3. 所有事件都被挂载到 document
  4. 和 dom 事件不一样 和 vue 事件也不一样

3)、传递自定义参数

正常传参数,最后一个参数是 event。

4、表单

  • 受控组件/非受控组件

  • 表单的基本使用 input textare select 用 value

  • checkbox radio 用 checked

    受控组件: 在HTML中,标签<input>、<textarea>、<select>的值的改变通常是根据用户输入进行更新。在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。

  1. 通过 value 和 onChange 实现。
  2. lable 中的for 使用 htmlFor 代替

非受控组件使用场景:

  • 必须手动操作 DOM 元素,setState 实现不了
  • 文件上传 <input type=file />
  • 某个富文本编辑器,需要传入 DOM 元素

受控组件 VS 非受控组件

  • 优先使用受控组件,符合 React 设计原则(数据驱动视图)。
  • 必须手动操作 DOM ,在使用非受控组件

5、组件使用

1)、props 传递数据

  • 在组件上定一个属性
  • 通过 this.props.属性 接收

2)、props 传递函数

  • 在组件上定一个属性
  • 通过 this.props.属性() 调用

3)、props 类型检查

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // JS原始类型,这些全部默认是可选的
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 可以直接渲染的任何东西,可以是数字、字符串、元素或数组
  optionalNode: PropTypes.node,

  // React元素
  optionalElement: PropTypes.element,

  // 指定是某个类的实例
  optionalMessage: PropTypes.instanceOf(Message),

  // 可以是多个值中的一个
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 可以是多种类型中的一个
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 只能是某种类型的数组
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 具有特定类型属性值的对象
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 具有相同属性值的对象
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 必选条件,可以配合其他验证器,以确保在没有提供属性的情况下发出警告
  requiredFunc: PropTypes.func.isRequired,

  // 必选条件,提供的属性可以为任意类型
  requiredAny: PropTypes.any.isRequired,

  // 自定义‘oneOfType’验证器。如果提供的属性值不匹配的它应该抛出一个异常
  // 注意:不能使用‘console.warn’ 和 throw
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 自定义‘arrayOf’或者‘objectOf’验证器。
  // 它会调用每个数组或者对象的key,参数前两个是对象它本身和当前key
  // 注意:不能使用‘console.warn’ 和 throw
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

6、setState

  1. 不可变值/数据
  2. 可能是异步更新
  3. 可能会被合并

1)、 不可变值

不能提前对 state 值进行修改,不能应该 state 设置之前的值

不能直接操作 state 值, 只能通过 setState 改变。

// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
    list1: this.state.list1.concat(100), // 追加
    list2: [...this.state.list2, 100], // 追加
    list3: this.state.list3.slice(0, 3), // 截取
    list4: this.state.list4.filter(item => item > 100), // 筛选
    list5: list5Copy // 其他操作
})
// 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值

// 不可变值 - 对象
this.setState({
    obj1: Object.assign({}, this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a: 100}
})
// 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值

2)、 可能是异步更新

在 React 库控制时,异步;否则同步。

同步的情况:

setTimeout,自己定义的 DOM 事件 等 非 React库控制时为同步执行

异步的情况:

React 库控制时,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。

React是怎样控制异步和同步的呢?

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。

3)、 可能会被合并

  • 传入对象,会被合并(类似 Object.assign )。结果值执行一次

      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
    
  • 传入的函数,不会被合并。

      // 执行结果是 +3
      this.setState((prevState, props) => {
          return {
              count: prevState.count + 1
          }
      })
      this.setState((prevState, props) => {
          return {
              count: prevState.count + 1
          }
      })
      this.setState((prevState, props) => {
          return {
              count: prevState.count + 1
          }
      })
    

4)、setState和useState的set函数

  1. 在正常的react的事件流里(如onClick等)
  • setState和useState中的set函数是异步执行的(不会立即更新state的结果)
  • 多次执行setState和useState的set函数,组件只会重新渲染一次
  • 不同的是,setState会更新当前作用域下的状态,但是set函数不会更新,只能在新渲染的组件作用域中访问到
  • 同时setState会进行state的合并,但是useState中的set函数做的操作相当于是直接替换,只不过内部有个防抖的优化才导致组件不会立即被重新渲染
  1. 在setTimeout,Promise.then等异步事件或者原生事件中
  • setState和useState的set函数是同步执行的(立即重新渲染组件)
  • 多次执行setState和useState的set函数,每一次的执行都会调用一次render

7、组件生命周期

1)、单组件

挂载时:constructor -> render -> componentDidMount

更新时:props、setState等 -> render -> componentDidUpdate

常用生命周期:

Vue

不常用生命周期:

Vue

React16.4版本之后使用了新的生命周期,它使用了一些新的生命周期钩子(getDerivedStateFromProps、getSnapshotBeforeUpdate),并且即将废弃老版的3个生命周期钩子(componentWillMount、componentWillReceiveProps、componentWillUpdate)

如图所示,我们可以看到,在组件第一次挂载时会经历:

构造器(constructor)=》修改state属性(getDerivedStateFromProps)=》组件挂载渲染(render)=》组件挂载完成(componentDidMount)

组件内部状态更新:

更新state属性(getDerivedStateFromProps)=》判断组件是否更新(shouldComponentUpdate)=》组件更新渲染(render)=》(getSnapshotBeforeUpdate)=》组件更新完成(componentDidUpdate)

组件卸载时执行:

组件销毁(componentWillUnmount) **注意:
**

新版本使用了getDerivedStateFromProps代替了componentWillMount、componentWillReceiveProps、componentWillUpdate三个钩子函数,如果在旧版本中使用将会警告提示。它必须要return一个null或者对象,并且会影响初始化的值以及修改的值。存在只有一个目的:让组件在 props 变化时更新 state。

getSnapshotBeforeUpdate钩子必须与componentDidUpdate搭配使用否则会报错。在旧版本中使用将会警告提示。必须要return一个null或者任何值,它将在最近一次渲染输出(提交到DOM节点)之前调用。

生命周期新增函数详解

static getDerivedStateFromProps(getDSFP)

首先这个新的方法是一个静态方法,在这里不能调用this,也就是一个纯函数。它传了两个参数,一个是新的nextProps ,一个是之前的prevState,所以只能通过prevState而不是prevProps来做对比,它保证了state和props之间的简单关系以及不需要处理第一次渲染时prevProps为空的情况。也基于以上两点,将原本componentWillReceiveProps里进行的更新工作分成两步来处理,一步是setState状态变化,更新 state在getDerivedStateFromProps里直接处理。

旧的React中componentWillReceiveProps方法是用来判断前后两个props是否相同,如果不同,则将新的props更新到相应的state上去。在这个过程中我们实际上是可以访问到当前props的,这样我们可能会对this.props做一些奇奇怪怪的操作,很可能会破坏state数据的单一数据源,导致组件状态变得不可预测。

而在getDerivedStateFromProps中禁止了组件去访问 this.props,强制让开发者去比较nextProps与prevState中的值,以确保当开发者用到getDerivedStateFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去访问this.props并做其他一些让组件自身状态变得更加不可预测的事情。

getSnapshotBeforeUpdate

在React开启异步渲染模式后,在执行函数时读到的DOM元素状态并不一定和渲染时相同,这就导致在componentDidUpdate中使用的DOM元素状态是不安全的(不一定是最新的),因为这时的值很有可能已经失效了。

与componentWillMount不同的是,getSnapshotBeforeUpdate会在最终确定的render执行之前执行,也就是能保证其获取到的元素状态与componentDidUpdate中获取到的元素状态相同。

这个方法并不常用,但它可能出现在UI处理中,如需要以特殊方式处理滚动位置的聊天线程等。并且会返回snapshot的值或null。

生命周期修改的深层原因

因为React 16引入了Fiber机制,把同步的渲染流程进化为了异步的渲染流程,这么做的原因是同步渲染流程有个弊端:一旦开始就不能停下,大工作量的渲染任务执行时,主线程会被长时间的占用,浏览器无法即时响应与用户的交互。

Fiber机制会把渲染任务拆解为多个小任务,并且每执行完一个小任务,就把主线程的执行权交出去,也就解决了上面的弊端。

然而,采用Fiber机制进行渲染时,render阶段没有副作用,可以被暂停,终止或重新启动。就是这个重新启动,会导致工作在render阶段的componentWillMount、componentWillReceiveProps、componentWillUpdate存在重复执行的可能,所以它们几个必须被替换掉。

2)、父子组件

和 vue 的完全一样。

8、React 高级属性 ---- 性能优化

1)、shouldComponentUpdate

返回 true ,表示可以渲染。返回 false 不可渲染。默认返回 true 。

shouldComponentUpdate(nextProps, nextState) {
    if (nextState.count !== this.state.count) {
        return true // 可以渲染
    }
    return false // 不重复渲染
}

既然有个功能 为什么默认返回 true , 为什么可定制化。

  • 默认父组件更新,子组件无条件更新
2)、PureComponent (纯组件) 和 React.memo
  • PureComponent, shouldComponentUpdate 中实现了浅比较 (class 组件)
  • memo 函数组件中的 PureComponent
  • 浅比较已经适应大部分情况(尽量不要做深度比较)
3)、不可变值 immutable.js
  • 彻底拥抱 不可变值
  • 基于共享数据(不是深拷贝),速度好
  • 有一定学习和迁移成本,按需引用

什么是 Immutable Data

  • mmutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象
  • Immutable 实现的原理是 Persistent Data Structure (持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变
  • 同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗, Immutable 使用了 Structural Sharing···· (结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

为什么要在React.js中使用Immutable

  • 它是一个完全独立的库,无论基于什么框架都可以用它。意义在于它弥补了 Javascript 没有不可变数据结构的问题
  • 由于是不可变的,可以放心的对对象进行任意操作。在 React 开发中,频繁操作state对象或是 store ,配合 immutableJS 快、安全、方便
  • 熟悉 React.js 的都应该知道, React.js 是一个 UI = f(states) 的框架,为了解决更新的问题, React.js 使用了 virtual dom , virtual dom 通过 diff 修改 dom ,来实现高效的 dom 更新。
  • 但是有一个问题。当 state 更新时,如果数据没变,你也会去做 virtual dom 的 diff ,这就产生了浪费。这种情况其实很常见
  • 当然你可能会说,你可以使用 PureRenderMixin 来解决呀, PureRenderMixin 是个好东西,我们可以用它来解决一部分的上述问题
  • 但 PureRenderMixin 只是简单的浅比较,不使用于多层比较。那怎么办?自己去做复杂比较的话,性能又会非常差
  • 方案就是使用 immutable.js 可以解决这个问题。因为每一次 state 更新只要有数据改变,那么 PureRenderMixin 可以立刻判断出数据改变,可以大大提升性能

9、React 高级属性 ---- 高阶组件 HOC (抽离公共逻辑)

一个高阶组件只是一个包装了另外一个 React 组件的 React 组件。这种形式通常实现为一个函数,本质上是一个类工厂(class factory)

高阶组件的作用就是实现组件复用,节省内存

  • 先定义一个函数式组价,传入一个参数,这个参数就是组件
  • 组件内返回一个class类组件,类名可以写也可以不写
  • 类组件内部可以写方法,数据,然后将参数当做组件返回出去,并将方法或者数据,传个这个组件

例子1:

    import React,{ Component } from 'react';
	
	const Hoc = ( Comp ) =>{//参数首字母必须大写,必须要有返回值,在下面使用
	    return class banner extends Component{ //类名可以省略,省略的话标签名就是以temp或者其他的代替,必须要有返回值
	        banner = () => {//这里是实现某个功能的函数代码
	            return 'zhangyue'
	        }
	        render () {
	            return (
	                <Comp banner = { this.banner }></Comp>//将参数当做一个组件返回出去
	            )
	        }
	    }
	}
	class A extends Component{
	    render () {
	        return (
	            <div>
	                <p> A组件 </p>
	                { this.props.banner() }//在下面使用了高阶组件后,这里就可以直接使用里面的方法了
	            </div>
	        )
	    }
	}
	
	class B extends Component{
	    render () {
	        return (
	            <div>
	                <p> B组件 </p>
	                { this.props.banner() }
	            </div>
	        )
	    }
	}
	
	const HocA = Hoc(A)//组件名必须首字母大写,将组件名当参数传进去,这样这个组件就有高阶组件内的方法了
	const HocB = Hoc(B)
	
	
	class C extends Component{
	    render () {
	        return (
	            <div>
	                <p> C组件 </p>
	                <HocA></HocA>//这里使用的高阶组件
	                <HocB></HocB>
	            </div>
	        )
	    }
	}
	export default C

例子2:

	import React from 'react'

	// 高阶组件
	const withMouse = (Component) => {
	    class withMouseComponent extends React.Component {
	        constructor(props) {
	            super(props)
	            this.state = { x: 0, y: 0 }
	        }
	  
	        handleMouseMove = (event) => {
	            this.setState({
	                x: event.clientX,
	                y: event.clientY
	            })
	        }
	  
	        render() {
	            return (
	                <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
	                    {/* 1. 透传所有 props 2. 增加 mouse 属性 */}
	                    <Component {...this.props} mouse={this.state}/>
	                </div>
	            )
	        }
	    }
	    return withMouseComponent
	}
	
	const App = (props) => {
	    const a = props.a
	    const { x, y } = props.mouse // 接收 mouse 属性
	    return (
	        <div style={{ height: '500px' }}>
	            <h1>The mouse position is ({x}, {y})</h1>
	            <p>{a}</p>
	        </div>
	    )
	}
	
	export default withMouse(App) // 返回高阶函数

10、React 高级属性 ---- Render Props (抽离公共逻辑)

给组件添加一个值为函数的属性,这个函数可以在组件渲染(render)的时候调用

核心思想: 通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件。

import React from 'react'
import PropTypes from 'prop-types'

class Mouse extends React.Component {
    constructor(props) {
        super(props)
        this.state = { x: 0, y: 0 }
    }
  
    handleMouseMove = (event) => {
      this.setState({
        x: event.clientX,
        y: event.clientY
      })
    }
  
    render() {
      return (
        <div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
            {/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
            {this.props.render(this.state)}
        </div>
      )
    }
}
Mouse.propTypes = {
    render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}

const App = (props) => (
    <div style={{ height: '500px' }}>
        <p>{props.a}</p>
        <Mouse render={
            /* render 是一个函数组件 */
            ({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
        }/>
        
    </div>
)

/**
 * 即,定义了 Mouse 组件,只有获取 x y 的能力。
 * 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
 */

export default App

HOC VS Render Porps

  • HOC: 模式简单,但会增加组件层级
  • Render Porps: 代码简洁,学习成本较高
  • 按需使用吧

11、React 高级属性 ---- 函数组件

定义一个函数 只接收 props 然后 return 内容。

  • 纯函数,输入 props ,输出 JSX。

  • 没有实例,没有生命周期,没有 state。

  • 不能扩展其他方法。

12、React 高级属性 ---- ref

  • React.createRef() 创建 ref
  • this.myRef.current 获取 DOM 元素

13、React 高级属性 ---- Portals (插槽)

Portals 提供了一种能让子节点渲染到父组件之外的方式。

React.createPortal(child,container);

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素,挂载到的位置。

render() {
    // // 正常渲染
    // return <div className="modal">
    //     {this.props.children} {/* vue slot */}
    // </div>

    // 使用 Portals 渲染到 body 上。
    // fixed 元素要放在 body 上,有更好的浏览器兼容性。
    return ReactDOM.createPortal(
        <div className="modal">{this.props.children}</div>,
        document.body // DOM 节点
    )
}

Portals 使用场景:

  • overflow: hidden (父组件设置了 BFC , BFC 不限制子组件的展示,让子组件逃离父组件 )
  • 父组件 z-index 值太小 (让子组件逃离父组件设置 合适的 z-index)
  • fixed 需要放在 body 第一层级的

一般都是应对 css 样式布局问题。

14、React 高级属性 ---- context

跨组件传递数据,解决 props 传递繁琐, redux 小题大做问题。

context api给出三个概念:React.createContext()、Provider、Consumer;

  • React.createContext() : 创建context对象

  • Provider: 数据的生产者,通过value属性接收存储的公共状态,来传递给子组件或后代组件。<Provider value={/* some value */}>

  • Consumer : 数据的消费者,通过订阅Provider传入的context的值,来实时更新当前组件的状态

      import React from 'react'
      // 创建 Context 填入默认值(任何一个 js 变量)
      const ThemeContext = React.createContext('light')
      // 底层组件 - 函数式组件
      function ThemeLink (props) {
          // const theme = this.context // 会报错。函数式组件没有实例,即没有 this
      
          // 函数式组件可以使用 Consumer
          return <ThemeContext.Consumer>
              { value => <p>link's theme is {value}</p> }
          </ThemeContext.Consumer>
      }
      // 底层组件 - class 组件
      class ThemedButton extends React.Component {
          // 指定 contextType 读取当前的 theme context。
          // static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
          render() {
              const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
              return <div>
                  <p>button's theme is {theme}</p>
              </div>
          }
      }
      ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。
      
      // 中间的组件再也不必指明往下传递 theme 了。
      function Toolbar(props) {
          return (
              <div>
                  <ThemedButton />
                  <ThemeLink />
              </div>
          )
      }
      
      class App extends React.Component {
          constructor(props) {
              super(props)
              this.state = {
                  theme: 'light'
              }
          }
          render() {
              return <ThemeContext.Provider value={this.state.theme}>
                  <Toolbar />
                  <hr/>
                  <button onClick={this.changeTheme}>change theme</button>
              </ThemeContext.Provider>
          }
          changeTheme = () => {
              this.setState({
                  theme: this.state.theme === 'light' ? 'dark' : 'light'
              })
          }
      }
      export default App
    

15、React 高级属性 ---- 异步组件

  • import() vue 中常用
  • React.lazy
  • React.Suspense

组件比较大,路由懒加载的时候 使用

import React from 'react'

const ContextDemo = React.lazy(() => import('./ContextDemo'))

class App extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            <p>引入一个动态组件</p>
            <hr />
            <React.Suspense fallback={<div>Loading...</div>}>
                <ContextDemo/>
            </React.Suspense>
        </div>
		// 验证方式:
        // 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
        // 2. 看 network 的 js 加载
    }
}

export default App`

16、Redux 使用

1)、基本概念

应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。

(1)、store state

Redux 应用只有一个单一的 store。唯一的一个。

Store 有以下职责:

  • 维持应用的 state;
  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

(2)、action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

(3)、reducer

Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。

(previousState, action) => newState

例子:

function todoApp(state = initialState, action) {
  // 这里暂不处理任何 action,
  // 仅返回传入的 state。
  return state
}

永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()。

2)、单项数据流

严格的单向数据流是 Redux 架构的设计核心。

Redux 应用中数据的生命周期遵循下面 4 个步骤:

  1. 调用 store.dispatch(action)。
  2. Redux store 调用传入的 reducer 函数。
  3. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
  4. Redux store 保存了根 reducer 返回的完整 state 树。

3)、react-redux

(1)、

使组件层级中的 connect() 方法都能够获得 Redux store。正常情况下,你的根组件应该嵌套在 中才能使用 connect() 方法。

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

(2)、connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
  • mapStateToProps: 这个函数允许我们将 store 中的数据作为 props 绑定到组件上。
  • mapDispatchToProps: 将 action 作为 props 绑定到组件上,也会成为组件自己的 props。
  • 不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给组件。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。
  • [options] (Object) 如果指定这个参数,可以定制 connector 的行为。一般不用。

(3)、mapStateToProps(state, ownProps), mapDispatchToProps(dispatch, ownProps)

mapStateToProps(state, ownProps):

  • 这个函数的第一个参数就是 Redux 的 store,我们从中摘取了 count 属性。你不必将 state 中的数据原封不动地传入组件,可以根据 state 中的数据,动态地输出组件需要的(最小)属性。
  • 函数的第二个参数 ownProps,是组件自己的 props。有的时候,ownProps 也会对其产生影响。

当 state 变化,或者 ownProps 变化的时候,mapStateToProps 都会被调用,计算出一个新的 stateProps,(在与 ownProps merge 后)更新给组件。

mapDispatchToProps(dispatch, ownProps):

  • 第一个参数就是 dispatch 方法
  • 同上

(3)、

4)、异步 action

Vue

需要引入中间件 redux-thunk

let store = createStore(
  combineReducers({...home,...log}),
  applyMiddleware(thunk,...),//加入中间件redux-thunk,applyMiddleware就是Redux的一个原生方法,将所有中间件组成一个数组,依次执行。
);

原来的store.dispatch方法只能接收一个普通的action对象作为参数,当我们加入了ReduxThunk这个中间件之后,store.dispatch还可以接收一个方法作为参数,这个方法会接收到两个参数,第一个是dispatch,等同于store.dispatch,第二个是getState,等同于store.getState,也就是说,现在可以这样来触发INCREASE:

store.dispatch((dispatch, getState) => dispatch({type: 'INCREASE'}));

5)、中间件

Vue

中间件 对原 dispatch 进行改造,获取原 dispatch 处理逻辑 然后传给 真正的 dispatch

Vue

17、React-router

  • 路由模式 (hash , H5 history), 同 vue-router
  • 路由配置 (懒加载,动态路由),同 vue-router

18、函数是编程

  • 纯函数
  • 不可变值

19、Vdom 和 diff 算法

回顾:

  • h 函数: 传入 tag 属性 子节点 返回 vnode 数据结构
  • vnode 数据结构
  • patch 函数
  • 只比较同一层级,不跨级比较
  • tag 不相同,则直接删掉重建,不在深度比较
  • tag 和 key 两者都相同 则认为是相同节点,不在深度比较

20、合成事件

  • event 是 SyntheticEvent (合成事件) 模拟出 DOM 事件的所有能力
  • 所有的事件都挂载到 document 上
  • 原生和合成执行顺序:首先DOM事件监听器被执行,然后事件继续冒泡至document,合成事件监听器被执行。
  • 和 vue 事件不同,和 原生 Dom 不同

event内容

  • event.preventDefault() 阻止默认行为
  • event.stopPropagetion() 阻止冒泡
  • event.nativeEvent 是原生事件
  • event.target 指向当前元素,即当前元素触发
  • event.currentTaget 指向当前元素 假象!!!

Vue

为什么要使用合成事件?

  • 更好的兼容性和跨平台
  • 挂载到 document , 减少内存消耗,避免频繁解绑
  • 方便事件的统一管理 (如事务机制)

16和17变化 image.png

  • 16版本先执行原生事件,当冒泡到document时,统一执行合成事件,
  • 17版本在原生事件执行前先执行合成事件捕获阶段,原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件
  • 原生的阻止事件流会阻断合成事件的执行,合成事件阻止后也会影响到后续的原生执行

在 React16 中,对 document 的事件委托都委托在冒泡阶段,当事件冒泡到 document 之后触发绑定的回调函数,在回调函数中重新模拟一次 捕获-冒泡 的行为,所以 React 事件中的e.stopPropagation()无法阻止原生事件的捕获和冒泡,因为原生事件的捕获和冒泡已经执行完了。

在 React17 中,对 React 应用根 DOM 容器的事件委托分别在捕获阶段和冒泡阶段。即:

  • 当根容器接收到捕获事件时,先触发一次 React 事件的捕获阶段,然后再执行原生事件的捕获传播。所以 React 事件的捕获阶段调用e.stopPropagation()阻止原生事件的传播。
  • 当根容器接受到冒泡事件时,会触发一次 React 事件的冒泡阶段,此时原生事件的冒泡传播已经传播到根了,所以 React 事件的冒泡阶段调用e.stopPropagation()不能阻止原生事件向根容器的传播,但是能阻止根容器到页面顶层的传播。

21、setState 和 batchUpdate

1)、setState

  • 有时异步(普通使用),有时同步(非 react setTimeout Dom 事件)
  • 有时合并 (对象形式),有时不合并 (函数形式)

真正判断是否同步或者异步:

  • 看是否命中 batchUpdate 机制
  • 判断 isBatchUpdates

2)、batchUpdate 机制

在React的生命周期和合成事件执行前后都有相应的钩子,分别是pre钩子和post钩子,pre钩子会调用batchedUpdate方法将isBatchingUpdates变量置为true,开启批量更新,而post钩子会将isBatchingUpdates置为false

Vue

isBatchingUpdates变量置为true,则会走批量更新分支,setState的更新会被存入队列中,待同步代码执行完后,再执行队列中的state更新。

而在原生事件和异步操作中,不会执行pre钩子,或者生命周期的中的异步操作之前执行了pre钩子,但是pos钩子也在异步操作之前执行完了,isBatchingUpdates必定为false,也就不会进行批量更新。

3)、transaction 事务机制

Vue

我们可以这样理解事务,react中用事务执行方法,就是用wrapper(称之为套套吧)把方法包裹起来,然后每个wapper中都提供一个initialize方法和一个close方法,当需要使用事务调用一个方法,例如上图中的anyMethod时,使用事务提供的perform方法,将需要执行的方法传入,这个时候就会按顺序执行wrapper.initalize,anyMethod,wrapper.close,而且,事务还支持多个事务的嵌套,当执行方法被多个wapper包裹时,事务会先按顺序执行所有的initalize方法,再执行anyMethod,最后按顺序执行所有的close函数,例如上图就表示会按以下顺序执行wrapper1.initalize,wrapper2.initalize,anyMethod,wrapper1.close,wrapper2.close

22、组件渲染过程

创建过程:

  • props state
  • render() 生成 vnode
  • patch(elem,vnode) 生成节点

注:patch react 中不一定是 patch 函数生成的,有可能是其他名字,原理和 patch 函数相同。

更新过程:

  • setState(newState) ---> dirtyComponents (有可能子组件)
  • render() 生成 newVnode
  • patch(elem,vnode)创建节点

patch 拆分两个阶段:

  1. renconciliation 阶段----执行 diff 算法,纯 js 计算
  2. commit 计算----将 diff 结果渲染到 DOM

如果不拆分可能存在的问题:

  • js 是单线程,且和 DOM 渲染共用一个线程
  • 当组件足够复杂,组件更新时计算和渲染都压力大
  • 同事再有 DOM 操作需求(动画、鼠标拖拽等),将卡顿..

解决方案 fiber

  • renconciliation 阶段进行任务拆分(commit 无法拆分)
  • DOM 需要渲染的时暂停,空闲时恢复
  • 通过 window.requestdleCallback(浏览器有兼容的问题) 判断 DOM 是否渲染完成

fiber架构思想:

改变了之前react的组件渲染机制,新的架构使原来同步渲染的组件现在可以异步化,可中途中断渲染,执行更高优先级的任务。释放浏览器主线程,

前面是react16以前的组建渲染方式。这就存在一个问题,

如果这是一个很大,层级很深的组件,react渲染它需要几十甚至几百毫秒,在这期间,react会一直占用浏览器主线程,任何其他的操作(包括用户的点击,鼠标移动等操作)都无法执行。

23、jsx 本质

// https://www.babeljs.cn/

// JSX 基本用法
const imgElem = <div id="div1">
    <p>some text</p>
    <img src={imgUrl}/>
</div>

// JSX style
const styleData = { fontSize: '30px',  color: 'blue' }
const styleElem = <p style={styleData}>设置 style</p>

// JSX 加载组件
const app = <div>
    <Input submitTitle={onSubmitTitle}/>
    <List list={list}/>
</div>

// JSX 事件
const eventList = <p onClick={this.clickHandler}>
    some text
</p>

// JSX list
const listElem = <ul>{this.state.list.map((item, index) => {
    return <li key={item.id}>index {index}; title {item.title}</li>
})}</ul>


// 总结
React.createElement('div', null, [child1, child2, child3])
React.createElement('div', {...}, child1, child2, child3)
React.createElement(List, null, child1, child2, '文本节点')
// h 函数
// 返回 vnode
// patch

第一个参数是 tag 标签 或者 组件 (如果是组件 继续拆分 为 tag 标签) 第二个参数是 属性 有可能是 null 后边的参数是子元素,有数组和正常参数,也有文本节点

  • React.createElement 即 h 函数,返回 vnode 节点
  • 第一个参数是 tag 标签 或者 组件
  • 组件名首字母大写,html tag 是小写

24、React 性能优化

  • 渲染列表时加 key
  • 自定义事件、Dom 事件及时销毁
  • 合理使用异步组件
  • 减少函数 bind this 的次数
  • 合理使用 shouldComponentUpdate PureComponent (纯组件) 和 React.memo
  • 合理使用 Immutable.js ( 不可变值 )
  • webpack
  • 前端通用的性能优化
  • 使用 ssr

25、React 和 Vue 的区别

共同点:

  • 都支持组件化
  • 都是数据驱动视图
  • 都使用 Vdom 操作 DOM

区别:

  • React 使用 JSX 拥抱 js, Vue 使用模板拥抱 html
  • React 函数式编程, Vue 是声明式编程
  • React 更多的是自力更生 (比较灵活),Vue 把想要的都给你 (比较固定,使用简单)