React

146 阅读12分钟

React创建组件的方式

1. 无状态函数式组件(推荐)
  特点:
    你无法使用State,展示性组件,接收Props,渲染DOM,而不关注其他逻辑
  代码:
    export default  function Button(props){  
      let {name} = props  
      const sayHi = () => {  
          alert(`Hi ${name}`);  
      }  
      return (  
          <div>  
              <h1>Hello, {name}</h1>  
              <button onClick ={sayHi}>Say Hi</button>  
          </div>  
      )  
    }

2. ES5原生方式(React.createClass)
  解释:
    React.createClass 是react刚开始推荐的创建组件的方式,这是ES5的原生的JavaScript来实现的React组件
  代码:
    const Button = React.createClass({  
      getInitialState//初始state
      getDefaultProps//初始props
      render() {return ()}
    });

3).React.ComponentES6形式)
  解释:
    React.Component 是以ES6的形式来创建react的组件的,是React推荐和常用的创建有状态组件的方式
    
4).React.PureComponent (高性能组件)
    特点:
      1.继承PureComponent时,不能再重写shouldComponentUpdate,否则会引发警告
      2.继承PureComponent时,进行的是浅比较,也就是说,如果是引用类型的数据,只会比较是不是同一个地址
      3.浅比较会忽略属性或状态突变的情况,其实也就是,数据引用指针没变而数据被改变的时候,
        也不新渲染组件。但其实很大程度上,我们是希望重新渲染的。
        所以,这就需要开发者自己保证避免数据突变。如果想使按钮被点击后可以正确渲染也很简单,
        将 `const words = this.state.words;` 改为`const words = this.state.words.slice(0)`;
        (在原来state的基础上复制出来一个新数组,所以引用地址当然变啦)

jsx是什么

JSX是看起来很像XML的JavasSript语法扩展
React遇到<就会当成HTML语法解析,遇到{}就会当做JS解析

为什么要使用jsx

1. 编译成js的时候优化过了,运行速度会更快
2. 对语法有严格要求,编译时不正确会报错
3. 语法比模板字符串更简洁

使用jsx的要求

只能有一个根节点

props类型检测

<!-- 第一步:下载引入npm包 -->
  import propType from 'prop-types';
  class Login extends React.Component{
    render(){return (<h1>{this.props.title}</h1>)}
  }

<!-- 第二步:设置默认props -->
  Login.defaultProps={
    title:'默认标题'
  }

<!-- 第三步:类型检测 -->
  Login.propTypes={
    title:propType.string
  }

react事件的this绑定

1. bind方法
    <div onClick = {this.methodfn.bind(this)}></div>

2. 构造函数内绑定
    constructor(props) {
      super(props);
      this.state = {};
      this.methodfn = this.methodfn.bind(this)
    }
    <div onClick = {this.methodfn}></div>

3. 箭头函数绑定
    <div onClick = {() => {this.methodfn}}></div>

4. ::绑定(不能传参)
    <div onClick = {::this.methodfn}></div>

ref的使用

1. String ref
  <input ref="myInput" />

2. callback ref
  <input ref="{(ele)=>{ this.myRef=ele }}" />

3. createRef()
  constructor(props){
    super(props)
    this.myRef=React.createRef();
  }
  <input ref={this.myRef} />

组件通讯

<!-- 父子组件: -->
  父传子:
    props传递
  子传父:(传递回调)
    class Parent extends React.Component{
      say(str){
        alert(str)
      }
      render(){
        return (<Child say={this.say}></Child>)
      }
    }
    class Child extends React.Component{
      render(){
        return (<button onClick={this.props.say('hello')}></button>)
      }
    }

<!-- 跨级组件间通信:(使用context对象实现生产消费模式) -->
      创建context createContext({}) 
      祖先生产 <Context.Provider value={}> 
      后代使用 <Context.Consumer></Context.Consumer>

<!-- 无关系组件 -->
    1. redux/mobx/flux
    2. context
    3. 使用事件发布订阅

React路由

<!-- 路由模式 -->
    1、BrowserRouter:
    浏览器的路由方式,也就是在开发中最常使用的路由方式
    2、HashRouter:
    在路径前加入#号成为一个哈希值,Hash模式的好处是,再也不会因为我们刷新而找不到我们的对应路径
    3、MemoryRouter:
    不存储history,所有路由过程保存在内存里,不能进行前进后退,因为地址栏没有发生任何变化
    4、NativeRouter:
    经常配合ReactNative使用,多用于移动端
    5、StaticRouter:
    设置静态路由,需要和后台服务器配合设置,比如设置服务端渲染时使用
<!-- 页面跳转 -->
    1.Link/NavLink标签的to属性
    2.this.props.history.push()
<!-- 传参 -->
    1、query方式 
    传参:this.props.history.push({pathname:'/list',query:{color:'red'}})
    接收:this.props.location.query
    优势 : 刷新地址栏,参数依然存在
    缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
    2、state方式
    传参:this.props.history.push({pathname:'/list',state:{color:'red'}})
    接收:this.props.location.state
    优缺点与query方式相同
    3、params方式(类似vue动态路由)
    定义:<Route path="list/:id"></Route>
    传参:this.props.history.push({pathname:'/list/001'})
    接收:this.props.match.params
    优势:传参优雅,传递参数可传对象;
    缺点:刷新地址栏,参数丢失
    4、search方式
    传参:<Link to="/list?id=001'"></Link>
    接收:this.props.location.search
    优缺点与params方式相同

路由标签和属性

  Route标签   定义路由
  switch标签  只匹配第一个
  redirect标签    重定向
  exact属性   完全匹配

生命周期执行过程

<!-- 1、实例化阶段 -->
  componentWillMount() 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
  render() react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
  componentDidMount() 组件渲染之后调用,只调用一次。

<!-- 2、更新阶段 -->
  componentWillReceiveProps(nextProps,nextState):当组件接收到新的 props 时,会触发该函数。在改函数中,通常可以调用 this.setState 方法来完成对 state 的修改
  shouldComponentUpdate:该方法用来拦截新的 props 或 state,然后根据事先设定好的判断逻辑,做出最后要不要更新组件的决定。
  componentWillUpdate:当上面的方法拦截返回 true 的时候,就可以在该方法中做一些更新之前的操作
  render:根据一系列的 diff 算法,生成需要更新的虚拟 DOM 数据。(注意:在 render 中最好只做数据和模板的组合,不应进行 state 等逻辑的修改,这样组件结构更加清晰
  componentDidUpdate:该方法在组件的更新已经同步到 DOM 中去后触发,我们常在该方法中做一 DOM 操作。

<!-- 3、销毁阶段 -->
  componentWillUnmount:我们通常会做一些取消事件绑定、移除虚拟 DOM 中对应的组件数据结构、销毁一些无效的定时器等工作

React16.3改动生命周期

<!-- 改动 -->
  react v16.3终于出来了,最大的变动莫过于生命周期去掉了以下三个
    componentWillMount
    componentWillReceiveProps
    componentWillUpdate
    
  同时为了弥补失去上面三个周期的不足又加了两个
    getDerivedStateFromProps
    getSnapshotBeforeUpdate

<!-- 描述 -->
  getDerivedStateFromProps(props,state)
    触发时间:在组件构建之后(虚拟dom之后,实际dom挂载之前) ,以及每次获取新的props之后。
    每次接收新的props之后都会返回一个对象作为新的state,返回null则说明不需要更新state.
    配合componentDidUpdate,可以覆盖componentWillReceiveProps的所有用法
    
  getSnapshotBeforeUpdate
    触发时间: update发生的时候,在render之后,在组件dom渲染之前。·
    返回一个值,作为componentDidUpdate的第三个参数。
    配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法。

React VirtualDOM

Virtual DOM是一个映射真实DOM的JavaScript对象,
如果需要改变任何元素的状态, 那么是先在Virtual DOM上进行改变
当有变化产生时,一个新的Virtual DOM对象会被创建并计算新旧Virtual DOM之间的差别,
之后这些差别会应用在真实的DOM上

React Diff算法

diff算法作为Virtual DOM的加速器,其算法的改进优化是React整个界面渲染的基础和性能的保障
同时也是React源码中最神秘的,最不可思议的部分 react diff算法的3个策略 Web UI 中DOM节点跨层级的移动操作特别少,
可以忽略不计 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。 
对于同一层级的一组子节点,它们可以通过唯一id进行区分。 
对于以上三个策略,react分别对tree diff,component diff,element diff进行算法优化

react的合成事件

优势:
  1. 防止绑定事件过多绑定或事件滥用造成内存占用和性能问题;
  2. 屏蔽浏览器底层差异

原理:
  当用户在为onClick添加函数时,React并没有将Click时间绑定在DOM上面。
  而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,
  React将事件内容封装交给中间层SyntheticEvent(负责所有事件合成)
  所以当事件触发的时候,对使用统一的分发函数dispatchEvent将指定函数执行

React对于css的模块化解决方案 (CSS Modules)

<!-- CSS Modules 实现了以下几点: -->
  1. 所有样式都是 local 的,解决了命名冲突和全局污染问题
  2. class 名生成规则配置灵活,可以此来压缩 class 名
  3. 只需引用组件的 JS 就能搞定组件所有的 JS 和 CSS
  4. 依然是 CSS,几乎 0 学习成本 使用了 CSS Modules 后,就相当于给每个 class 名外加加了一个 :local,
  5. 以此来实现样式的局部化,如果你想切换到全局模式,使用对应的 :global。

React.createClass与React.Component区别

  • 函数this自绑定

React.createClass创建的组件,其每一个成员函数的this都有React自动绑定,任何时候使用,直接使用this.method即可,函数中的this会被正确设置。

React.Component创建的组件,其成员函数不会自动绑定this,需要开发者手动绑定,否则this不能获取当前组件实例对象。

  • 组件属性类型propTypes及其默认props属性defaultProps配置不同

React.createClass在创建组件时,有关组件props的属性类型及组件默认的属性会作为组件实例的属性来配置,其中defaultProps是使用getDefaultProps的方法来获取默认组件属性的

React.Component在创建组件时配置这两个对应信息时,他们是作为组件类的属性,不是组件实例的属性,也就是所谓的类的静态属性来配置的

  • 组件初始状态state的配置不同

React.createClass创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态;

React.Component创建的组件,其状态state是在constructor中像初始化组件属性一样声明的

什么是受控组件

比如input写成:value和onInput的形式,value根据onInput事件的返回值再setState流向组件的value;
值是可控制的,或其他表单类组件

setState有几个参数

参数1:对象,要改变的值

参数2:执行完修改的回调函数

装饰器

简单来说,装饰器就是用一个代码包装另一个代码的简单方式,就是简单的将一个函数包装成为另一个函数

JavaScript中装饰器使用特殊的语法,使用@作为标识符,且放置在被装饰代码之前

React行内样式

<div style={{color:'#fff',fontSize:'16px'}}></div>

解释withRouter

当一个非路由组件也想访问到当前路由的match,location,history对象,那么withRouter将是一个非常好的选择

import { withRouter } from 'react-router'
const FirstTest = withRouter(MyComponent)

redux的使用

1.定义action-type.js文件  (用于声明操作的TYPE类型)
//存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。

2.定义actions.js文件  (用于声明发起dispatch(action)的操作函数)
//reducer是纯函数,只承担计算state的任务

3.定义reducers.js文件 (用于声明reducer函数,此函数接收来自dispatch的action操作)
//combineReducers方法可以合并多个reducer

4.定义store.js文件 (用于创建和抛出仓库,根index.js的Provider挂载store对象实现自顶向下的数据流)
//redux-persist库实现持久化存储

5.页面引入react-redux库的connct包装页面使用
connect(mapStateToProps,mapDispatchToProps)(Index)

applyMiddleware

它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。

redux-logger & redux-thunk & redux-promise & redu-saga

redux-logger  
createLogger(applyMiddleware(【某其他插件】,logger)) 
日志中间件 (必须放最后,仅且在开发模式下使用)

redux-thunk
createLogger(applyMiddleware(thunk)) 
异步解决方案 , 改造store.dispatch 使得其可以接受函数作为参数

redux-promise
createLogger(applyMiddleware(promiseMiddleware))  
异步解决方案 , 改造store.dispatch 使得其可以接受Promise作为参数

redu-saga  
1.统一action的形式 
2.集中处理异步等存在副作用的逻辑 
3.通过转化effects函数,可以方便进行单元测试 
4.完善和严谨的流程控制,可以较为清晰的控制复杂的逻辑

中间件的意义

对某些功能进行转化改造封装,满足某些需要的场景

DvaJs的使用

定义:
    1. 轻量级的应用框架,
    2. 整合简化了redux和redux-saga 的数据流方案,
    3. 还额外内置了 react-router 和 fetch
    
特性:
    1. 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
    2. elm 概念,通过 reducers, effects 和 subscriptions 组织 model
    3. 插件机制,比如dva-loading可以自动处理loading状态,不用一遍遍地写showLoading和hideLoading
    4. 支持 HMR,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR

DvaJs中models的组成

state:  仓库存储的基础状态
Subscription:  订阅
Effect:  副作用方法
Reducer:  纯函数 可预测返回值

React Hooks

    概念由来:
      1. 因为纯函数式组件没有状态 + 没有this + 没有生命周期
      2. 类组件功能齐全却很重,需要继承React.Component
      
    概念定义:
      1. React Hooks就是加强版的函数组件,我们可以完全不使用 class,就能写出一个全功能的组件
      2. React Hooks的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来
      
    常用钩子:
      /* 钩子一律使用 use前缀命名。所以,你自己定义的钩子都要命名为useXXX */
      1. useState() // 状态钩子
          import {useState} from 'react';
          const [ count, setCount ] = useState(0) // count初始值变量 setCount设置值方法
      2. userContext() // 共享状态钩子
          import { useContext } from 'react';
          const AppContext = React.createContext({})
          const { name } = useContext(AppContext)
          return (
            <AppContext.Provider value={{name: 'hook测试'}}>
            <A/>
            <B/>
            </AppContext.Provider>
          )
      3. userReducer() // Action钩子 但是无法使用中间件
          import {useReducer} from 'react'
          const AddCount = () => {
            const reducer = (state, action) =>  {
              if(action.type === 'add'){
                return {...state,count: state.count +1,}
              }else {return state}
            }
            const addcount = () => { 
              dispatch({type: 'add'})
            }
            const [state, dispatch] = useReducer(reducer, {count: 0})
            return (<><p>{state.count}</p><button onClick={addcount}>count++</button></>)
          }
          export default AddCount
      4. useEffect() // 副作用钩子 
      /* 接受两个参数,第一个参数是你要进行的异步操作,第二个参数是一个数组,用来给出Effect的依赖项。*/
      /* 只要这个数组发生变化,useEffect()就会执行。当第二项省略不填时,useEffect()会在每次组件渲染时执行*/
          import React, { useState, useEffect } from 'react'
          const AsyncPage = ({name}) => {
          const [loading, setLoading] = useState(true)
          const [person, setPerson] = useState({})
            useEffect(() => {
              setLoading(true)
              setTimeout(()=> {
                setLoading(false)
                setPerson({name})
              },2000)
            },[name])
            return (<>{loading?<p>Loading...</p>:<p>{person.name}</p>}</>)
          }
          const PersonPage = () =>{
            const [state, setState] = useState('')
            const changeName = (name) => {setState(name)}
            return (<><AsyncPage name={state}/>
                <button onClick={() => {changeName('名字1')}}>名字1</button>
                <button onClick={() => {changeName('名字2')}}>名字2</button>
              </>)
          }
          export default PersonPage 
          
    概念特点:
      1. 完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。
         但是如果你不想,你不必现在就去学习或使用 Hook2. 100% 向后兼容的。 Hook 不包含任何破坏性改动。
      3. 现在可用。 Hook 已发布于 v16.8.04. 没有计划从 React 中移除 class5. Hook不会影响你对React概念的理解。恰恰相反,Hook为已知的React概念
         提供了更直接的 API:props, state,context,refs 以及生命周期。

高阶函数

形式:传入函数,返回一个新的函数
    
功能:增强函数功能

高阶组件

使用:传入组件,返回一个新的组件
    
目的:增强组件功能

高阶组件的种类

侵入式组件:依赖组件传入的状态和参数

非侵入式组件:不依赖状态和参数

React常用高阶组件

1. connect
2. withRouter
3. From.create()

路由按需加载

1. react-loadable
2. Loadable:loader,loading
3. dva中的dynamic