React项目笔记总结

2,367 阅读19分钟

前言

该笔记是自己做项目时的一些随笔总结,以及如何从RC-->RF的一个过度心得,现在更多是作为一个字典以及备忘录的形式存在。可能有很多不足或者有误的地方,望指正。

项目启动与技术栈选型

  1. 项目技术栈
  • React
  • React-Router-Dom
  • Redux + React-Redux + Redux-Thunk
  • Axios 数据请求
  • LeanCloud云服务 在线数据存取操作
  • AntDesign UI组件库

一、搭建React开发环境

  1. 切换镜像
    切换安装镜像源 npm config set registry https://registry.npm.taobao.org
    配置后可通过下面方式来验证是否成功npm config get registry
  1. 初始化react项目
    npx create-react-app  [项目名称]
    npm run start(yarn start)  启动项目
import React from 'react';  //视图交互
import ReactDOM from 'react-dom';   //真实dom操作
ReactDOM.render(
    <App />,
    document.getElementById('root')
)


  1. Es7 React插件常用
imp import moduleName from 'module'
imr import React from 'react'
imd import {  } from 'module'
imc 
rcc 生成类组件结构
rfc 生成函数组件结构
rfcredux 生成状态机

二、JSX语法

语法糖,本质是为了更加方便的实现效果 虚拟DOM 与 diff算法 渲染JSX结构 我们需要一种数据格式,表达一段DOM结构

  1. 通过虚拟DOM来表达
{
    tag:'div',
    type:'tag',
    attr:{
        'className':'App'
    },
}
  1. JSX表达DOM结构(React)
let nodes = <div className="App">
                asdfasdfa
                <span>我是span</span>
            </div>
  1. JSX渲染组件的流程 JSX结构--虚拟DOM--Diff算法比对--新的虚拟DOM--真实DOM

  2. JSX中数据的动态绑定

  • 字符串渲染
  • 基本运算(加减乘除模)
  • 模板字符串
  • 三目运算
  • 函数调用
  • 逻辑运算(与或非)
  • JSX嵌套
  1. 属性、样式动态绑定 className的动态绑定
<div className={isActive?'box active':'box'}></div>

其他属性

  • input value\checked\disabled
  • img src 注意表单元素的部分属性,例如value value需要用defaultValue checkbox需要用defaultChecked
<input defaultValue={str}/>
<input type="checkbox" defaultChecked={isCheck}/>

三、组件分类

组件名称首字母必须大写(调用组件时不大写会报错)

  1. 函数式组件

在React较低版本中,我们把函数式组件,又称为无状态组件 新版本中,可以使用Hooks来让函数式组件也拥有状态

export default function FnComp(){
    return <div>组件结构</div>
}
  1. 类组件

通过class类的方式,定义组件 需要引入import React,{Component} from 'react'

import React,{Component} from 'react'
class ClassDemo extends Component{
  render(){
    return <h1>这是类组件</h1>
  }
}
export default ClassDemo

四、事件函数的定义

  1. 普通函数定义方式(定义生命周期)

必须要在constructor中修正this指向

constructor(){  //自有属性
    super() //为ClassDemo提供this,调用了父类的constructor
    this.state = {
      isActive:false
    }
    this.handleBg = this.handleBg.bind(this) //修正this指向
  }

handleBg(){  //【1】通过普通语法定义事件函数,注意this指向
    let {isActive} = this.state;
    this.setState({
        isActive:!isActive //通过事件触发state变化
    })
}
  1. 箭头函数定义方式
handleBg = ()=>{
    let {isActive} = this.state;
    this.setState({
      isActive:!isActive //【3】通过事件触发state变化
    })
  }

五、组件通信

总结:(类)父元素变量传参,如果子组件是函数组件,用实参porps接住使用,如果子组件是类组件,用this.props接住直接使用!如果需要回传参数,父组件需要传一个function,子组件用嵌套匿名函数回传参数

(一)、父子通信 props

  • 函数式组件

通过组件的第一个参数接受props

//父组件直接传参
<Job jobname={jobname}/>
//子组件通过props接受并使用
import React from 'react'
export default function Job(props) {
  return (
    <h3>岗位名称:{props.jobname}</h3>
  )
}
  • 类组件

通过this.props获取props参数

//父组件直接传参
<ClassJob 
  jobname={jobname} 
  salary={salary} 
  handlesalary={this.handleSalary}
/>
//子组件定义时继承原型上有props, 通过this.props参数
render() {
    let {jobname,salary,handleSalary} = this.props
    return (
      <div>
        <h3>岗位名称:{jobname}</h3>
        <p>薪资:{salary}K</p>
        <button onClick={()=>{
            handlesalary(idx,5)
          }}>涨薪</button>
      </div>
    )
  }

(二)、子父通信

父组件传递事件函数给子组件,以改变父属性的父子通信

  1. 在父组件定义state
  2. 在父组件中定义用以修改state的事件函数 handleSalary
  3. 调用子组件的时候,将父组件的事件函数传递给子组件
constructor(){
    super()
    this.state = {
      joblist:[
        {
          jobname:"前端工程师",
          salary:8
        }
      ]
    }
  }
handleSalary=(idx,m)=>{
    let {joblist} = this.state
    joblist[idx].salary += m
    this.setState({
      joblist
    })
  }
<ClassJob 
  jobname={jobname} 
  salary={salary} 
  handlesalary={this.handleSalary}
/>
  1. 在子组件中,按需触发父组件传递的事件函数,引发父组件state的变化

如果需要传参,子组件调用时需要嵌套匿名箭头函数

render() {
    let {jobname,salary,idx} = this.props
    let {handlesalary} = this.props
    return (
      <div>
        <h3>岗位名称:{jobname}</h3>
        <p>薪资:{salary}K</p>
        {salary>=20
          ?''
          :<button onClick={()=>{
            handlesalary(idx,5)
          }}>涨薪</button>
        }
      </div>
    )
  }

(三)、父组件调用子组件方法

1、如果是在方法组件中调用子组件(>= react@16.8),可以使用 forwardRef 和 useImperativeHandle:

const { forwardRef, useRef, useImperativeHandle } = React;

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    getAlert() {
      alert("getAlert from Child");
    }
  }));
  return <h1>Hi</h1>;
});

const Parent = () => {
  const childRef = useRef();
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.getAlert()}>Click</button>
    </div>
  );
};

2、如果是在类组件中调用子组件(>= react@16.4),可以使用 createRef:

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('getAlert from Child');
  }

  render() {
    return <h1>Hello</h1>;
  }
}

六、数据渲染

(一)、列表渲染

{
  this.state.joblist.map((job,index)=>{
    return <ClassJob />
  })
}

(二)、条件渲染

{salary>=20
  ? ''
  : <button>涨薪</button>
}

七、获取真实DOM

不建议大规模使用
管理焦点,文本选择或媒体播放。 触发强制动画。 集成第三方 DOM 库。 文档

  1. 使用createRef生成一个用以获取DOM的属性 myInput
import React, { Component,createRef } from 'react'
this.myInput = React.createRef()
  1. 将该属性通过ref绑定给指定jsx中的某个节点元素
<input type="text" ref={this.myInput} onKeyUp={this.handleInput}/>
  1. 在组件的其他逻辑代码中,通过this.myInput获取真实DOM并操作
    注意使用了createRef就不要用onchange事件,受控跟不受控两种方式,不要混合使用
handleInput=(ev)=>{
  let v = this.myInput.current.value
  if(ev.keyCode===13){
      let v = this.myInput.current.value
      this.setState({
        msglist:[
          ...this.state.msglist,
          v
        ]
      },()=>{ //setState执行完毕后才会触发回调
        this.myInput.current.value = ''
      })
    }
}

八、解决setState异步方法的回调

解决setstate异步问题:使用回调(注意回调地狱问题)

this.setState({},()=>{})
this.setState({
  msglist:[
    ...this.state.msglist,
    v
  ]
},()=>{ //setState执行完毕后才会触发回调
  this.myInput.current.value = ''
})

九、生命周期函数

**### **小知识PureComponent(选择性更新)

能够自动识别新旧state数据的不同,从而选择性更新

常用生命周期函数 生命周期函数图

初始化阶段

  • constructor
  • render
  • componentDidMount 发起在线请求,获取数据包

更新阶段

  • render
  • componentDidUpdate

销毁阶段

  • componentWillUnmount

十、leancloud

模拟接口
应用列表 使用详解

十一、webpack介绍

项目开发打包工具
文档

  1. 模块化、组件化开发 ---webpack---代码整合
  2. 高级语法(ES6语法、React语法)---webpack---浏览器可以识别的语法
  3. 提供比较方便的开发环境

plugin插件与loader的区别

  • Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。用于解析不同类型语法的组件(如:React)

    • 使用babel-loader协助webpack编译react语法
    • css-loader style-loader 解析样式文件及其语法
  • Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

  • Babel 是一个 JavaScript 编译器。

    • babel的预设:react预设、ES语法预设等

devserver的配置使用

启动一个本地服务,监听代码变化,重新打包代码并自动刷新浏览器进行预览

十二、HOC (Hight-Order-Component) 高阶组件

本质是一个函数 作用就是处理一个已有组件,为这个组件追加新结构或方法后,返回新的组件 HOC文档

  1. 使用场景 react-router withRouter() 向组件注入路由数据 react-redux connect() 向组件注入store数据

十三、8个Hooks新特性(函数式编程)

常用Hooks特性

(一)、useState

让函数式组件拥有state及操作方法

  import React,{useState} from 'react'
  let [num,setNum] = useState(100) //定义了一个state名为num,默认值为100,可以通过setNum方法来重新设置num的值

(二)、useEffect

1.第一个参数,接收一个函数作为参数
2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
3.返回一个函数,先执行返回函数,再执行参数函数

  useEffect(()=>{
    setList(['赵敏','灭绝师太111'])
    (async()=>{
      let res = await getTodoList()
      setList(res.data.results)
    })()
  },[]) //依赖可以适当添加

(三)、useLayoutEffect

useLayoutEffect在DOM更新之后执行;useEffect在render渲染结束后执行。执行示例代码会发现useLayoutEffect永远比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束

(四)、useMemo

搭配React.memo做性能优化:存储一个变量,可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)
1.接收一个函数作为参数
2.同样接收第二个参数作为依赖列表(与useEffect、useLayoutEffect进行对比学习)
3.返回的是一个值。返回值可以是任何,函数、对象等都可以

  //子组件初始值换成useMemo包起来;子组件只会在初始化状态时渲染一次
  const info = {
      name: 'Even',
      age: 22
  }
  const info = useMemo( () => {   //包裹子组件初始化数据
      return {
          name: 'Even',
          age: 22
      }
  },[]) //依赖可以适当添加
  
  或者
  import React, { useContext, useMemo } from 'react';
  export default (props= {}) => {
  const { state, dispatch } = useContext(MyContext);
  return useMemo(() => {
      return (
      <div></div>
      }, [state.count, state.number. state.step, dispatch]);
  }

(五)、useCallback

类同useMome,useCallback是对传过来的回调函数优化,返回的是一个函数;useMemo返回值可以是任何,函数,对象等都可以

 function Parent () {
      const [num, setNum] = useState(1)
      const [age, setAge] = useState(18)
      const getDoubleNum = useCallback( () => {
          console.log(`获取双倍Num${num}`)
          return 2 * num
      },[num] )
      return (
          <div onClick={ () => {setNum( num => num+1 )} }>
              这是一个函数式组件————num:{  getDoubleNum() }
              <br></br>
              age的值为————age:{ age }
              <br></br>
              set.size:{set.size}
              <Child callback={ getDoubleNum() }></Child>
          </div>
      )
  }
  
  function Child(props) {
      useEffect( () => {
          console.log('callback更新了') //这里代表的是需要跟随传入内容的改变而同步进行的操作
      },[props.callback])
      return (
          <div>
              子组件的getDoubleNum{props.callback}
          </div>
      )
  }

子组件更新优化简单总结使用场景判断:
+ 在子组件不需要父组件的值和函数的情况下,只需要使用memo函数包裹子组件即可
+ 如果有函数传递给子组件,使用useCallback
+ 其他使用useMemo

(六)、useRef

useRef 不仅仅是用来管理 DOM ref 的,它还相当于 this , 可以存放任何变量
父组件获取子组件方法、参数并操作子组件

//父组件中:
const detailInfoRef = useRef<DrawerInstance>(null);
然后在detailInfoRef.current中解构出需要使用的方法、值

//子组件中:
//需要使用forwardRef包裹子组件、再useImperativeHandle传递参数,如:
import { forwardRef, memo, useImperativeHandle } from 'react';
const DrawerBillGoods = forwardRef<propsType,refType>(({percent}, ref) => {
  const { openDrawer, closeDrawer } = useDrawer();
  useImperativeHandle(ref, () => ({
    openDrawer,
    closeDrawer,
  }));
  return (
    <div>I am update every {percent} seconds</div>
  )
})
  
// memo()可接受2个参数,第一个参数为纯函数的组件,
  第二个参数用于对比props控制是否刷新
export default memo(CSlider, (prevProps, nextProps) => {
  return prevProps.percent === nextProps.percent;
})

//如:
export default memo(SellerDrawer, (next, prev) =>
  _.isEqual(next.data, prev.data),
)

(七)、useContext

需要引入useContetx,createContext两个内容
通过createContext创建一个context句柄
Context.Provider来确定数据共享范围
通过value来分发内容 在子组件中,通过useContext(Context句柄)来获取数据

  const Context = createContext(null)     //创建/可以分发值
  function StateFunction () {
      const [num, setNum] = useState(1)
      return (
          <div>
              <button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button>
              这是一个函数式组件——num:{  num }
              <Context.Provider value={num}>  //确定范围且分发值
                  <Item3></Item3>
                  <Item4></Item4>
              </Context.Provider>
          </div>
      )
  }
  
  function Item3 () {
      const num = useContext(Context) //直接使用
      return (
          <div>子组件3: { num }</div>
      )
  }

(八)、useReducer

> 1、对于单个组件复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。      
> 2、可以通过useReducer在函数式组件中使用Redux。作用是可以从状态管理的工具中获取到想要的状态 
使用方法1
function ReducerDemo(){
  const [ count , dispatch ] =useReducer((state,action)=>{
      switch(action){
          case 'add':
              return state+1
          case 'sub':
              return state-1
          default:
              return state
      }
  },0)
  return (
     <div>
         <h2>现在的分数是{count}</h2>
         <button onClick={()=>dispatch('add')}>Increment</button>
         <button onClick={()=>dispatch('sub')}>Decrement</button>
     </div>
  )

}
使用方法2
  const store = {
      age:18,
      num:1
  }	//数据仓库(含多个state)
  
  const reducer = (state=store, action) => {
      switch(action.type){
          case 'add':
              return {
                  ...state,
                  num: action.num+1
              }
          default:
              return {
                  ...state
              }
      }
  } //管理者
  
  function StateFunction () {
      const [state,dispacth] = useReducer(reducer,store)  //获取状态和改变状态的动作
      return (
          <div>
              <button onClick={ () => {
                  dispacth({
                      type: 'add',
                      num: state.num
                  })
              } }>
                  增加num的值+1
              </button>
              这是一个函数式组件——num:{  state.num }
          </div>
      )
  }

十四、Fragment代码片段

渲染一组元素,Fragment本身不会渲染为标签
效果等同 < ></>

import React,{Fragment} from 'react'
export default function Frag1() {
  return (
    <Fragment>
      <p>列表元素</p>
      <p>列表元素</p>
    </Fragment>
  )
}

十五、props.children插槽

return (    //组件中
  <div>
    <h1>演示props.children</h1>
    {this.props.children}
  </div>
)

<SlotDemo>  //调用时
  <p>传入的新标签</p>
  <p>传入的新标签</p>
</SlotDemo>

十六、PropTypes类型验证

//组件中,安装引入使用
import PropTypes from 'prop-types'
PropTypeDemo.propTypes  = { //为类组件添加类型验证
  jobname:PropTypes.string,
  salary:PropTypes.number
}
//使用时
<PropTypeDemo jobname="前端开发工程师" salary={20}/>

十七、Context组件通信

可用状态机替换

  • 使用流程
    • 定义Context对象
    • 在组件中使用Provider提供数据
    • 在其他组件CompC中提取Context数据

十八、react路由相关模块区分

印记中文 react-router文档

(一)、基本使用

  1. 安装
yarn add react-router-dom
  1. 在App组件外包裹BrowserRouter或HashRouter
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
  1. 在需要动态展示组件的位置(如:Home组件中),使用Route做路由映射
<Route path="/frag" component={FragDemo}/>
<Route path="/frag" exact component={FragDemo}/>
//exact 表示完全匹配path
  1. 使用Link实现路由切换
<Link to="/frag">Fragment演示</Link>
// NavLink具有动态激活效果
<NavLink to="/frag/2333" activeClassName="active"></NavLink>

(二)、路由传参

  • 设置路由形参
<Route path="/frag/:id" component={FragDemo}/>
  • 传递路由实参
<Link to="/frag/2333"></Link>
  • 提取路由参数并使用
<h2>路由参数:{props.match.params.id}</h2>
函数式组件路由参数在  props
类组件路由参数在  this.props

(三)、编程式导航

  • 绑定事件
  • 触发路由切换
    ==编程式导航传参==
handleProd(target){
  //不传参
  // this.props.history.push(target)  

  //传参方法1  接参:props.location.state
  // this.props.history.push(target,66666) 

  //传参方法2  接参:props.location.state
  this.props.history.push({  
    pathname:target,
    state:{
      id:88888
    }
  }) 
}

(四)、路由嵌套

跟主路由配置'基本使用'流程一样 参考 Product/index.js

(五)、路由守卫

render属性的方法函数返回的组件,会被渲染到对应的Route位置

<Route path="/product" render={()=>{    //回调灵活使用
  let {isLogin} = this.state; //如登录态
  if(isLogin){
    return <Product />
  }else{
    return <Login handleLogin={this.handleLogin}/>
  }
}}/>

(六)、switch和重定向

在所有的Route外包裹Switch组件 路由发生变化的时候,Switch会依次去匹配Route的路径,如果有一个路径成功了,后续的Route就不再进行匹配

<Switch>
  <Route path="/effect" component={EffectOnline}/>
  
  <Route path="/404" component={NotFound}/>
  <Redirect from="*" to="/404"/>
</Switch>

(七)、withRouter 按需使用

路由守卫返回的组件也没有this.props,需要手动使用withRouter

不通过路由引导进行展示的组件内部,默认this.props中是没有路由对象的

withRouter可以让这类组件内部的this.props也能拥有路由对象;比如:默认展示组件home使用this.props

class Home extends Component {}
export default withRouter(Home)

(八)、路由原理分析

手写react路由

  1. Link组件
  • 通过a标签改变hash值
<Link to="/pro">菜单1</Link>
  1. Route组件
  • 监听浏览器地址栏hash变化 onhashchange
  • 拿Route组件的path属性 与 hash的路径 进行判断
<Route path="/pro1" component={组件}/>

十九、Redux介绍

文档

  1. Redux

状态机数据的动态变化,需要手动订阅subscribe

  1. Redux + React-Redux

不需要手动订阅

  1. Redux + React-Redux + @rematch/core

语法非常接近vuex 设计模式 (观察者模式)

(一)、核心概念

  • Store 生成状态机数据
  import {createStore} from 'redux'
  import reducer from '../reducer'
  let store = createStore(reducer)
  export default store
  • reducer 根据用户不同的需求,生产不同的数据包
  • Action 描述用户需求的对象
{
  type:'increment', //action的动作描述
  payload:10   //action携带的参数
}

(二)、Redux

  1. reducer

必须是纯函数 ,结果可预测的数据包

function reducer(state,action对象){
  if(action.type){
    return 数据包 //根据dispatch传递的type类型返回对应数据
  }
}
  1. store状态机
  • getState() 用以获取状态机数据的方法(数据不是动态的)
  • subscribe(()=>{}) 在view视图层中,订阅状态机数据的变化,从而做出响应
  • dispatch() view视图层向store状态机派发action描述的方法,reducer通过不同的type返回不同的值
  componentDidMount(){
    this.setState({
      num:store.getState()
    })
    store.subscribe(()=>{
      console.log('Num组件察觉到了store数据的变化',store.getState());
      this.setState({
        num:store.getState()
      })
    })
  }
  
  <button onClick={()=>{
    store.dispatch({
      type:'increment'
    })
  }}>+</button>

(三)、redux + react-redux

react-redux文档

删除商品:方法放在reducer中

  • 引入connect并注入状态机
  • 引入准备好的actioncreator
  • 将actioncreator传入connect使之变成一个对action对应的dispatch
  • 使用事件调用reducer中的方法 <DEL_GOOD>
  1. action

一个用以描述数据变动方式的对象
涉及到本地存储之类的操作不要在action中执行,去reducer中相应位置操作

{
  type:'',
  payload:''
}
  1. ActionCreator

一些能够生成action的方法函数

function increment(){
  return {
    type:'',
    payload:''
  }
}
  1. 安装react-redux
yarn add react-redux
  1. 在项目入口文件index.js中,从根组件注入store状态机
import {Provider} from 'react-redux'
import store from './store'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
  1. 在Num.js中提取store状态机中的==数据==

数据在reducer中已被注入,所以不用引入

<!--
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。
它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI
组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
-->

import {connect} from 'react-redux' //【1】使用connect方法关联状态机
class Num extends Component { }
const mapStateToProps = (state)=>{  //【2】此处的state就是connect注入的状态机数据包
  return {  //【3】此处return的值,将作为props出现在Num组件中
    count:state.??  //count是自定义的props名称
  }
}
export default connect(mapStateToProps)(Num)
// mapStateToProps 的作用,是为了把状态机中的数据引用到当前组件的props中去
  1. 在Btn.js组件中,提取状态机中的dispatch==方法==

方法需要importy引入

import React, { Component } from 'react'
import {connect} from 'react-redux' //【1】引入connect方法来关联状态机
import {
  decrement,
  increment
} from '../../action/index'  //【2】准备好的actionCreator

class Btn extends Component {
  render() {
    let {children,increment,decrement} = this.props 
    //【4】此处的increment,decrement已经变为了一个跟action对应的dispatch
    let disp = children=='+'? increment : decrement
    return (
      <button onClick={disp}>{children}</button>
    )
  }
}

export default connect(null,{
  increment,  //【3】将actionCreator传入connect中
  decrement
})(Btn)

(四)、redux + react-redux + redux-thunk [常见方案]

文档

redux-thunk使得状态管理器具有异步action的能力

异步请求发起位置

  • 组件内部发起? 异步请求用于渲染组件
  • 异步action? 异步请求用于操作状态机

哪些场景数据包适合放在状态机中?

  • 跨组件共享
  • 购物车、个人信息、地址管理 等
  1. 安装
yarn add redux-thunk
  1. 在store/index.js中引入并配置
import {createStore,combineReducers, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
let reducer = combineReducers({cartReducer,countReducer})
// applyMiddleware配置
let store = createStore(reducer,applyMiddleware(thunk))
  1. 在action/index.js中定义实现异步action
export const incrementSync = (payload)=>{  //异步action
  return dispatch =>{  
    //当用户触发异步action的时候,将dispatch进行拦截
    //等到异步操作结束后,再手动dispatch一个action
    setTimeout(()=>{
      dispatch(increment())  //用户在合适的条件下,自己手动dispatch
    },2000)
  }
}
  1. 在组件中调用异步action

跟调用同步action的流程一样 参考BtnSync.js

(五)、@rematch/core 的使用流程

文档

简化了store状态机的定义流程 参考 React(理论周)/day05/learn-redux 项目中的 learn-rematch分支

(六)、redux的HOOKS写法(useSelector, useDispatch )

import { useSelector, useDispatch } from 'react-redux';
import { addAction, reduceAction } from './store/actions/action';

const App = (props: any) => {
    const { num } = useSelector((state: any) => state.numReducer);
    const dispatch = useDispatch();
    return (
    <>
       <h2>全局数据store:{num}</h2>
        <button onClick={() => {
          dispatch(addAction())
        }}>+</button>
         <button onClick={() => {
          dispatch(reduceAction())
        }}>-</button>
    </>
    )
}

二十、react项目的自定义配置

实现的目标:

如何关闭EsLint 如何在项目中使用less语法 antd的自定义主题

  1. 自定义配置需要的包 react-app-rewired customize-cra

customize-cra提供具体某个配置项 react-app-rewired 可以让我们以新的方式启动项目,并让上面提供的配置项生效

(一)、资源引用路径的别名配置

@ 指向 src @action 指向 src/action

跳转config-overrides.js配置文件

addWebpackAlias({
    '@':path.resolve(__dirname,'src'),
    '@action':path.resolve(__dirname,'src/action'),
    '@api':path.resolve(__dirname,'src/api'),
    '@assets':path.resolve(__dirname,'src/assets'),
    '@components':path.resolve(__dirname,'src/components'),
    '@utils':path.resolve(__dirname,'src/utils')
})

(二)、自定义配置的流程

  1. 安装依赖
yarn add react-app-rewired customize-cra --dev
  1. 在项目根目录下新建config-overrides.js文件并配置
const {
  override,
  disableEsLint
} = require('customize-cra')

module.exports = override(
  disableEsLint()   //关闭eslint代码检测
)
  1. 在package.json中调整scripts命令
  "scripts": {
-   "start": "react-scripts start",    原有命令
+   "start": "react-app-rewired start",  新命令
  }

(三)、addLessLoader配置

  1. 安装样式预编译包
yarn add --dev less less-loader
  1. 调整config-overrides.js配置文件
addLessLoader({   //less文件编译配置
  lessOptions: {
    javascriptEnabled: true,
    modifyVars: { '@primary-color': '#A80000' },
  }
})

(四)、装饰器的配置流程

语法糖

  1. 使用装饰器语法前
class Login extends Component{

}
export default connect(mapState)(Login)
  1. 使用装饰器
@connect(mapState)
class Login extends Component{

}
  1. 装饰器语法文档 关于装饰器语法
@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true
  1. 安装bebel库
yarn add @babel/plugin-proposal-decorators
  1. 配置config-overrides.js

为了让脚手架环境支持装饰器语法 文档

  1. vscode对装饰器语法的识别配置

如果代码能运行,但是vscode提示语法错误,则需要配置

"settings": {
    "problems.decorations.enabled": false,
    "javascript.implicitProjectConfig.experimentalDecorators": true
},

实战 · 富文本编辑器

  1. 什么是富文本?
  • 富文本中会携带标签跟样式
    • vue 中可以使用 v-html
    • 小程序中 rich-text
    • uniapp rich-text

如何在React中集成第三方 wangEditor Ueditor tinymce

富文本编辑器使用流程

  1. 在React中集成富文本编辑器流程
  • 安装、引入
yarn add wangeditor
import E from 'wangeditor'
  • 获取一个富文本编辑器的容器DOM节点 createRef

  • 在需要展示富文本编辑器的组件中的componentDidMount中初始化编辑器

this.e = new E(this.editor.current) //富文本编辑器实例
this.e.create()
this.e.config.zIndex = 1
  • 通过指定方法提取富文本编辑器内容
let intro = this.e.txt.html()

实战 · 高德地图

在React中集成地图API 高德地图api

  1. 注册登录
  2. 新建应用并创建key
  3. 在项目public/index.html中引入高德地图方法包(js包)
  4. 在需要展示地图的组件中调用AMap方法初始化地图
var map = new AMap.Map(this.container.current, {
    resizeEnable: true
});

实战 · 用户角色控制

不同权限控制手段

  • 登录权限 得到roles角色
  • 通过roles筛选路由数据包,动态渲染符合权限要求的导航菜单 【用户看不到菜单】
  • 通过roles筛选路由数据包,动态渲染符合权限要求的Route
  • 后端在执行数据库操作前,需要对当前用户权限做判断 【后端开发完成】
  • 按钮级别的权限控制 【根据实际情况决定】
  • 路由映射关系数据包,放在服务器端动态下发 【可选】

路由添加rolus[]键值对[含能访问的角色],后续优化按钮级别权限时,[{user:root,C:true,R:false]},{user:other,R:true}]

  • 根据登录后状态机中存放的roles判断菜单和路由渲染情况
    • includes(a)数组api用于判断数组中是否存在a
let bool = roles.includes(role) && title
return bool 
    ? 
    <Menu.Item key={itm.path}>{itm.title}</Menu.Item>
    : ''
  • 路由控制需要用404兜底,渲染时基本步骤同菜单,需要用 取消正常路由渲染时多出来的404组件

实战 · Echarts的基本使用

Echarts官网

  1. 安装
yarn add echarts
  1. 在需要展示图表的组件中,引入echarts提供的方法
方法1import {init} from 'echarts'
方法2:
// import * as echarts from 'echarts' 
  1. 为图表展示准备一个DOM容器

  2. 调用echarts的init方法初始化图表并做自定义配置

var myChart = init(this.chart1.current);
// 指定图表的配置项和数据
var option = {};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);

react 打包时

"build": "react-scripts build",
改成
"build": "react-app-rewired build",