React笔记 && Taro笔记

这篇博客详细记录了React组件间的多种通讯方式,包括props、Provider、Consumer、Context、EventEmitter、路由传参、ref、redux、MobX、flux以及新特性hooks的使用。此外,还介绍了React的组件定义、state改变、事件监听、样式引入、动态组件等开发细节。同时,博主还探讨了Taro的CLI安装、项目初始化、编译配置和路由功能,并分享了如何搭建redux环境以及与dva的结合使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React基础

1 组件通讯

1.1 props

子组件

import React from "react";
import PropTypes from "prop-types";
import { Button } from "antd";

export default class EightteenChildOne extends React.Component {
  static propTypes = { //propTypes校验传入类型,详情在技巧11
    name: PropTypes.string
  };

  click = () => {
    // 通过触发方法由子组件调用父组件方法并传参
    this.props.eightteenChildOneToFather("这是 props 改变父元素的值");
  };

  render() {
    return (
      <div>
        {/*this.props.name由父组件传递过来*/}
        <div>这是通过 props 传入的值{this.props.name}</div>
        <Button type="primary" onClick={this.click}>
          点击改变父元素值
        </Button>
      </div>
    );
  }
}

父组件

{/*父组件定义eightteenChildOneToFather监听子组件方法调用*/}
<EightteenChildOne name={'props 传入的 name 值'} eightteenChildOneToFather={(mode)=>this.eightteenChildOneToFather(mode)}></EightteenChildOne> 

// 或者
<EightteenChildOne name={'props 传入的 name 值'} eightteenChildOneToFather={this.eightteenChildOneToFather.bind(this)}></EightteenChildOne>

props 传多个值时:
传统写法

const {dataOne,dataTwo,dataThree} = this.state
<Com dataOne={dataOne} dataTwo={dataTwo} dataThree={dataThree}>

升级写法

<Com {...{dataOne,dataTwo,dataThree}}>

1.2 props 升级版

原理:子组件里面利用 props 获取父组件方法直接调用,从而改变父组件的值
注意: 此方法和 props 大同小异,都是 props 的应用,所以在源码中没有举例

调用父组件方法改变该值

// 父组件
state = {
  count: {}
}
changeParentState = obj => {
    this.setState(obj);
}
// 子组件
onClick = () => {
    this.props.changeParentState({ count: 2 });
}

1.3 Provider,Consumer和Context

1.Context在 16.x 之前是定义一个全局的对象,类似 vue 的 eventBus,如果组件要使用到该值直接通过this.context获取

//根组件
class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple",text: "item text"};
  }

  render() {
    const {messages} = this.props || {}
    const children = messages && messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}

MessageList.childContextTypes = {
  color: React.PropTypes.string
  text: React.PropTypes.string
};

//中间组件
class Message extends React.Component {
  render() {
    return (
      <div>
        <MessageItem />
        <Button>Delete</Button>
      </div>
    );
  }
}

//孙组件(接收组件)
class MessageItem extends React.Component {
  render() {
    return (
      <div>
        {this.context.text}
      </div>
    );
  }
}

MessageItem.contextTypes = {
  text: React.PropTypes.string //React.PropTypes在 15.5 版本被废弃,看项目实际的 React 版本
};

class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}

Button.contextTypes = {
  color: React.PropTypes.string
};

2.16.x 之后的Context使用了Provider和Customer模式,在顶层的Provider中传入value,在子孙级的Consumer中获取该值,并且能够传递函数,用来修改context 声明一个全局的 context 定义,context.js

import React from 'react'
let { Consumer, Provider } = React.createContext();//创建 context 并暴露Consumer和Provider模式
export {
    Consumer,
    Provider
}

父组件导入

// 导入 Provider
import {Provider} from "../../utils/context"

<Provider value={name}>
  <div style={{border:'1px solid red',width:'30%',margin:'50px auto',textAlign:'center'}}>
    <p>父组件定义的值:{name}</p>
    <EightteenChildTwo></EightteenChildTwo>
  </div>
</Provider>

子组件

// 导入Consumer
import { Consumer } from "../../utils/context"
function Son(props) {
  return (
    //Consumer容器,可以拿到上文传递下来的name属性,并可以展示对应的值
    <Consumer>
      {name => (
        <div
          style={{
            border: "1px solid blue",
            width: "60%",
            margin: "20px auto",
            textAlign: "center"
          }}
        >
        // 在 Consumer 中可以直接通过 name 获取父组件的值
          <p>子组件。获取父组件的值:{name}</p>
        </div>
      )}
    </Consumer>
  );
}
export default Son;

1.4 EventEmitter

EventEmiter 传送门 使用 events 插件定义一个全局的事件机制

1.5 路由传参

1.params

<Route path='/path/:name' component={Path}/>
<link to="/path/2">xxx</Link>
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.name

2.query

<Route path='/query' component={Query}/>
<Link to={{ pathname : '/query' , query : { name : 'sunny' }}}>
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
读取参数用: this.props.location.query.name

3.state

<Route path='/sort ' component={Sort}/>
<Link to={{ pathname : '/sort ' , state : { name : 'sunny' }}}> 
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state

4.search

<Route path='/web/search ' component={Search}/>
<link to="web/search?id=12121212">xxx</Link>
this.props.history.push({pathname:`/web/search?id ${row.id}`});
读取参数用: this.props.location.search

这个在 react-router-dom: v4.2.2有 bug,传参跳转页面会空白,刷新才会加载出来

5.优缺点

1.params在HashRouter和BrowserRouter路由中刷新页面参数都不会丢失
2.state在BrowserRouter中刷新页面参数不会丢失,在HashRouter路由中刷新页面会丢失
3.query:在HashRouter和BrowserRouter路由中刷新页面参数都会丢失
4.query和 state 可以传对象

1.6 onRef

原理:onRef 通讯原理就是通过 props 的事件机制将组件的 this(组件实例)当做参数传到父组件,父组件就可以操作子组件的 state 和方法

EightteenChildFour.jsx

export default class EightteenChildFour extends React.Component {
  state={
      name:'这是组件EightteenChildFour的name 值'
    placeholder: '请输入'
  }

  componentDidMount(){
    this.props.onRef(this) // 将EightteenChildFour传递给父组件
    console.log(this)
    // this.props.onRef()方法
  }

  click = () => {
    this.setState({name:'这是组件click 方法改变EightteenChildFour改变的name 值'})
  };

	handleChange = (e) => {
    console.log(e.target.value)
    this.setState({
      name: '这是组件EightteenChildFour的Input 值'+ e.target.value
    })
  }

  render() {
    return (
      <div>
        <div>{this.state.name}</div>
        <Input type="text" onChange={this.handleChange} placeholder={this.state.placeholder} />
        <Button type="primary" onClick={this.click}>
          点击改变组件EightteenChildFour的name 值
        </Button>
      </div>
    );
  }
}

eighteen.jsx

state = {
  eightteenChildFour: '',
}
{/*触发handleClickEightteenChildFourFunc方法调用EightteenChildFour 子组件click方法*/}
<Button onClick={this.handleClickEightteenChildFourFunc}>EightteenChildFour</Button>

<EightteenChildFour onRef={this.eightteenChildFourRef}></EightteenChildFour>

handleClickEightteenChildFourFunc = () => {
  console.log(this.state.eightteenChildFour)
  this.state.eightteenChildFour.click()
  /*this.state.eightteenChildFour.setState({
    name: '123'
  })*/
}

eightteenChildFourRef = (ref)=>{
  console.log('eightteenChildFour的Ref值为')
  // 获取的 ref 里面包括整个组件实例
  console.log(ref)
  this.setState({
    eightteenChildFour: ref
  })
  // 调用子组件方法
  ref.click()
}

1.7 ref

原理:就是通过 React 的 ref 属性获取到整个子组件实例,再进行操作

EightteenChildFive.jsx

// 常用的组件定义方法
export default class EightteenChildFive extends React.Component {
  state={
      name:'这是组件EightteenChildFive的name 值'
  }

  click = () => {
    this.setState({name:'这是组件click 方法改变EightteenChildFive改变的name 值'})
  };

  render() {
    return (
      <div>
        <div>{this.state.name}</div>
        <Button type="primary" onClick={this.click}>
          点击改变组件EightteenChildFive的name 值
        </Button>
      </div>
    );
  }
}

eighteen.jsx

// 钩子获取实例
componentDidMount(){
    console.log('eightteenChildFive的Ref值为')
      // 获取的 ref 里面包括整个组件实例,同样可以拿到子组件的实例
    console.log(this.refs["eightteenChildFiveRef"])
  }

// 组件定义 ref 属性
<EightteenChildFive ref="eightteenChildFiveRef"></EightteenChildFive>

1.8 redux

redux 是一个独立的事件通讯插件,这里就不做过多的叙述 请戳传送门:

1.9 MobX

MobX 也是一个独立的事件通讯插件,这里就不做过多的叙述请戳传送门:

1.10 flux

flux 也是一个独立的事件通讯插件,这里就不做过多的叙述请戳传送门:

1.11 hooks

1.hooks 是利用 userReducer 和 context 实现通讯,下面模拟实现一个简单的 redux
2.核心文件分为 action,reducer,types
action.js

import * as Types from './types';

export const onChangeCount = count => ({
    type: Types.EXAMPLE_TEST,
    count: count + 1
})

reducer.js

import * as Types from "./types";
export const defaultState = {
  count: 0
};
export default (state, action) => {
  switch (action.type) {
    case Types.EXAMPLE_TEST:
      return {
        ...state,
        count: action.count
      };
    default: {
      return state;
    }
  }
};

types.js

export const EXAMPLE_TEST = 'EXAMPLE_TEST';

eightteen.jsx

export const ExampleContext = React.createContext(null);//创建createContext上下文

// 定义组件
function ReducerCom() {
  const [exampleState, exampleDispatch] = useReducer(example, defaultState);

  return (
    <ExampleContext.Provider
      value={{ exampleState, dispatch: exampleDispatch }}
    >
      <EightteenChildThree></EightteenChildThree>
    </ExampleContext.Provider>
  );
}

EightteenChildThree.jsx // 组件

import React, {  useEffect, useContext } from 'react';
import {Button} from 'antd'

import {onChangeCount} from '../../pages/TwoTen/store/action';
import { ExampleContext } from '../../pages/TwoTen/eighteen';

const Example = () => {

    const exampleContext = useContext(ExampleContext);

    useEffect(() => { // 监听变化
        console.log('变化执行啦')
    }, [exampleContext.exampleState.count]);

    return (
        <div>
            <p>值为{exampleContext.exampleState.count}</p>
            <Button onClick={() => exampleContext.dispatch(onChangeCount(exampleContext.exampleState.count))}>点击加 1</Button>
        </div>
    )
}

export default Example;

3.hooks其实就是对原有React 的 API 进行了封装,暴露比较方便使用的钩子;

4.钩子有:

钩子名作用
useState初始化和设置状态
useEffectcomponentDidMount,componentDidUpdate和componentWillUnmount和结合体,所以可以监听useState定义值的变化
useContext定义一个全局的对象,类似 context
useReducer可以增强函数提供类似 Redux 的功能
useCallback记忆作用,共有两个参数,第一个参数为一个匿名函数,就是我们想要创建的函数体。第二参数为一个数组,里面的每一项是用来判断是否需要重新创建函数体的变量,如果传入的变量值保持不变,返回记忆结果。如果任何一项改变,则返回新的结果
useMemo作用和传入参数与 useCallback 一致,useCallback返回函数,useDemo 返回值
useRef获取 ref 属性对应的 dom
useImperativeMethods自定义使用ref时公开给父组件的实例值
useMutationEffect作用与useEffect相同,但在更新兄弟组件之前,它在React执行其DOM改变的同一阶段同步触发
useLayoutEffect作用与useEffect相同,但在所有DOM改变后同步触发

5.useImperativeMethods

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeMethods(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

1.12 slot

slot 就是将父组件的标签传给子组件,类似vue 的 v-slot
场景:有些组件只是共用部分dom 逻辑,里面有部分逻辑是独立的

// 父组件文件
import SlotChild from 'SlotChild'

<SlotChild
slot={<div>这是父组件的 slot</div>}>
</SlotChild>

// 子组件
子组件直接获取 this.props.slot 就可获取到内容

1.13 对比

方法优点缺点
props不需要引入外部插件兄弟组件通讯需要建立共同父级组件,麻烦
props 升级版不需要引入外部插件,子传父,不需要在父组件用方法接收同 props
Provider,Consumer和Context不需要引入外部插件,跨多级组件或者兄弟组件通讯利器状态数据状态追踪麻烦
EventEmitter可支持兄弟,父子组件通讯要引入外部插件
路由传参可支持兄弟组件传值,页面简单数据传递非常方便父子组件通讯无能为力
onRef可以在获取整个子组件实例,使用简单兄弟组件通讯麻烦,官方不建议使用
ref同 onRef同 onRef
redux建立了全局的状态管理器,兄弟父子通讯都可解决引入了外部插件
mobx建立了全局的状态管理器,兄弟父子通讯都可解决引入了外部插件
flux建立了全局的状态管理器,兄弟父子通讯都可解决引入了外部插件
hooks16.x 新的属性,可支持兄弟,父子组件通讯需要结合 context 一起使用
slot支持父向子传标签

redux , mobx和flux对比

方法介绍
redux1.核心模块:Action,Reducer,Store;2. Store 和更改逻辑是分开的;3. 只有一个 Store;4. 带有分层 reducer 的单一 Store;5. 没有调度器的概念;6. 容器组件是有联系的;7. 状态是不可改变的;8.更多的是遵循函数式编程思想
mobx1.核心模块:Action,Reducer,Derivation;2.有多个 store;3.设计更多偏向于面向对象编程和响应式编程,通常将状态包装成可观察对象,一旦状态对象变更,就能自动获得更新
flux1.核心模块:Store,ReduceStore,Container;2.有多个 store;

2.require.context()

这个是 webpack 的 api,这个在 vue 技巧中有介绍,因为 Vue 和 React 工程都是基于 webpack打包,所以在 react 也可以使用

const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
  const name = path.basename(key, '.vue')
  modules[name] = files(key).default || files(key)
})

3.Decorator

定义:decorator是ES7的一个新特性,可以修改class的属性

import React from 'react'
import Test from '../../utils/decorators'

@Test
//只要Decorator后面是Class,默认就已经把Class当成参数隐形传进Decorator了。
class TwentyNine extends React.Component{
    componentDidMount(){
        console.log(this,'decorator.js') // 这里的this是类的一个实例
        console.log(this.testable)
    }
    render(){
        return (
            <div>这是技巧23</div>
        )
    }
}

export default TwentyNine

decorators.js

function testable(target) {
  console.log(target)
  target.isTestable = true;
  target.prototype.getDate = ()=>{
    console.log( new Date() )
  }
}

export default testable

很多中间件,像 redux 里面就封装了Decorator的使用

4.使用 if...else

场景:有些时候需要根据不同状态值页面显示不同内容

import React from "react";

export default class Four extends React.Component {
  state = {
    count: 1
  };
  render() {
    let info
    if(this.state.count===0){
      info=(
        <span>这是数量为 0 显示</span>
      )
    } else if(this.state.count===1){
      info=(
        <span>这是数量为 1 显示</span>
      )
    }
    return (
      <div>
        {info}
      </div>
    );
  }
}

5.state 值改变的五种方式

方式 1

let {count} = this.state
this.setState({count:2})

方式 2:callBack

this.setState(({count})=>({count:count+2}))

方式 3:接收 state 和 props 参数

this.setState((state, props) => {
    return { count: state.count + props.step };
});

方式 4:hooks

const [count, setCount] = useState(0)
// 设置值
setCount(count+2)

方式 5:state 值改变后调用

this.setState(
    {count:3},()=>{
        //得到结果做某种事
    }
)

6.监听states 变化

1.16.x 之前使用componentWillReceiveProps

componentWillReceiveProps (nextProps){
  if(this.props.visible !== nextProps.visible){
      //props 值改变做的事
  }
}

注意:有些时候componentWillReceiveProps在 props 值未变化也会触发,因为在生命周期的第一次render后不会被调用,但是会在之后的每次render中被调用 = 当父组件再次传送props

2.16.x 之后使用getDerivedStateFromProps,16.x 以后componentWillReveiveProps也未移除

export default class Six extends React.Component {
  state = {
    countOne:1,
    changeFlag:''
  };
  clickOne(){
    let {countOne} = this.state
    this.setState({countOne:countOne+1})
  };
  static getDerivedStateFromProps (nextProps){
    console.log('变化执行')
    return{
      changeFlag:'state 值变化执行'
    }
  }
  render() {
    const {countOne,changeFlag} = this.state
    return (
      <div>
        <div>
         <Button type="primary" onClick={this.clickOne.bind(this)}>点击加 1</Button><span>countOne 值为{countOne}</span>
        <div>{changeFlag}</div>
        </div>
      </div>
    );
  }
}

7.组件定义方法

方式 1:ES5 的Function 定义

function FunCom(props){
  return <div>这是Function 定义的组件</div>
}
ReactDOM.render(<FunCom name="Sebastian" />, mountNode)

// 在 hooks 未出来之前,这个是定义无状态组件的方法,现在有了 hooks 也可以处理状态

方式 2: ES5的 createClass 定义

const CreateClassCom = React.createClass({
  render: function() {
  return <div>这是React.createClass定义的组件</div>
  }
});

方式 3:ES6 的 extends

class Com extends React.Component {
  render(){
    return(<div>这是React.Component定义的组件</div>)
  }
}

调用

export default class Seven extends React.Component {
  render() {
    return (
      <div>
        <FunCom></FunCom>
        <Com></Com>
      </div>
    );
  }
}

区别: ES5的 createClass是利用function模拟class的写法做出来的es6; 通过es6新增class的属性创建的组件此组件创建简单.

8.通过 ref 属性获取 component

方式 1:也是最早的用法,通过 this.refs[属性名获取] 也可以作用到组件上,从而拿到组件实例

class RefOne extends React.Component{
  componentDidMount() {
    this.refs['box'].innerHTML='这是 div 盒子,通过 ref 获取'
  }
  render(){
    return(
      <div ref="box"></div>
    )
  }
}

方式 2:回调函数,在dom节点或组件上挂载函数,函数的入参是dom节点或组件实例,达到的效果与字符串形式是一样的,都是获取其引用

class RefTwo extends React.Component{
  componentDidMount() {
    this.input.value='这是输入框默认值';
    this.input.focus();
  }
  render(){
    return(
      <input ref={comp => { this.input = comp; }}/>
    )
  }
}

方式 3:React.createRef() React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上,该ref的current属性,将能拿到dom节点或组件的实例

class RefThree extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myRef.current);
  }
  render(){
    return <input ref={this.myRef}/>
  }
}

方式 4:React.forwardRef
React 16.3版本后提供的,可以用来创建子组件,以传递ref

class RefFour extends React.Component{
  constructor(props){
    super(props);
    this.myFourRef=React.forwardRef();
  }
  componentDidMount(){
    console.log(this.myFourRef.current);
  }
  render(){
    return <Child ref={this.myFourRef}/>
  }
}

子组件通过React.forwardRef来创建,可以将ref传递到内部的节点或组件,进而实现跨层级的引用。forwardRef在高阶组件中可以获取到原始组件的实例.这个功能在技巧 18 会着重讲

9.static 使用

场景:声明静态方法的关键字,静态方法是指即使没有组件实例也可以直接调用

export default class Nine extends React.Component {
  static update(data) {
    console.log('静态方法调用执行啦')
  }
  render() {
    return (
      <div>
        这是 static 关键字技能
      </div>
    );
  }
}

Nine.update('2')

注意: 1.ES6的class,我们定义一个组件的时候通常是定义了一个类,而static则是创建了一个属于这个类的属性或者方法
2.组件则是这个类的一个实例,component的props和state是属于这个实例的,所以实例还未创建
3.所以static并不是react定义的,而加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,所以也是无法访问到 this
4.getDerivedStateFromProps也是通过静态方法监听值,详情请见技巧 6

10.constructor和super

回顾:
1.谈这两个属性之前,先回顾一下ES6 函数定义方法
2.每一个使用class方式定义的类默认都有一个constructor函数, 这个函数是构造函数的主函数, 该函数体内部的this指向生成的实例
3.super关键字用于访问和调用一个对象的父对象上的函数

export default class Ten extends React.Component {
  constructor() { // class 的主函数
    super() // React.Component.prototype.constructor.call(this),其实就是拿到父类的属性和方法
    this.state = {
      arr:[]
    }
  }  
  render() {
    return (
      <div>
        这是技巧 10
      </div>
    );
  }
}

11.PropTypes

场景:检测传入子组件的数据类型
类型检查PropTypes自React v15.5起已弃用,请使用prop-types
方式 1:旧的写法

class PropTypeOne extends React.Component {
  render() {
    return (
      <div>
        <div>{this.props.email}</div>
        <div>{this.props.name}</div>
      </div>
    );
  }
}

PropTypeOne.propTypes = {
  name: PropTypes.string, //值可为array,bool,func,number,object,symbol
  email: function(props, propName, componentName) { //自定义校验
    if (
      !/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(
        props[propName]
      )
    ) {
      return new Error(
        "组件" + componentName + "里的属性" + propName + "不符合邮箱的格式"
      );
    }
  },
};

方法 2:利用 ES7 的静态属性关键字 static

class PropTypeTwo extends React.Component {
  static propTypes = {
      name:PropTypes.string
  };
  render() {
    return (
      <div>
        <div>{this.props.name}</div>
      </div>
    );
  }
}

12.使用类字段声明语法

场景:可以在不使用构造函数的情况下初始化本地状态,并通过使用箭头函数声明类方法,而无需额外对它们进行绑定

class Counter extends Component {
  state = { value: 0 };

  handleIncrement = () => {
    this.setState(prevState => ({
      value: prevState.value + 1
    }));
  };

  handleDecrement = () => {
    this.setState(prevState => ({
      value: prevState.value - 1
    }));
  };

  render() {
    return (
      <div>
        {this.state.value}

        <button onClick={this.handleIncrement}>+</button>
        <button onClick={this.handleDecrement}>-</button>
      </div>
    )
  }
}

13.异步组件

1.场景:路由切换,如果同步加载多个页面路由会导致缓慢

2.核心 API:
loader:需要加载的组件
loading:未加载出来的页面展示组件
delay:延迟加载时间
timeout:超时时间

3.使用方法:
安装 react-loadable ,babel插件安装 syntax-dynamic-import. react-loadable是通过webpack的异步import实现的

const Loading = () => {
  return <div>loading</div>;
};

const LoadableComponent = Loadable({
  loader: () => import("../../components/TwoTen/thirteen"),
  loading: Loading
});

export default class Thirteen extends React.Component {
  render() {
    return <LoadableComponent></LoadableComponent>;
  }
}

4.Loadable.Map()
并行加载多个资源的高阶组件

14.动态组件

场景:做一个 tab 切换时就会涉及到组件动态加载
实质上是利用三元表达式判断组件是否显示

class FourteenChildOne extends React.Component {
    render() {
        return <div>这是动态组件 1</div>;
    }
}

class FourteenChildTwo extends React.Component {
    render() {
        return <div>这是动态组件 2</div>;
    }
}

export default class Fourteen extends React.Component {
  state={
      oneShowFlag:true
  }
  tab=()=>{
      this.setState({oneShowFlag:!this.state.oneShowFlag})
  }
  render() {
    const {oneShowFlag} = this.state
    return (<div>
        <Button type="primary" onClick={this.tab}>显示组件{this.state.oneShowFlag?2:1}</Button>
        {this.state.oneShowFlag?<FourteenChildOne></FourteenChildOne>:<FourteenChildTwo></FourteenChildTwo>}
    </div>);
  }
}

如果是单个组件是否显示可以用短路运算

oneShowFlag&&<FourteenChildOne></FourteenChildOne>

15.递归组件

场景:tree组件
利用React.Fragment或者 div 包裹循环

class Item extends React.Component {
  render() {
    const list = this.props.children || [];
    return (
      <div className="item">
        {list.map((item, index) => {
          return (
            <React.Fragment key={index}>
              <h3>{item.name}</h3>
              {// 当该节点还有children时,则递归调用本身
              item.children && item.children.length ? (
                <Item>{item.children}</Item>
              ) : null}
            </React.Fragment>
          );
        })}
      </div>
    );
  }
}

16.受控组件和不受控组件

受控组件:组件的状态通过React 的状态值 state 或者 props 控制

class Controll extends React.Component {
  constructor() {
    super();
    this.state = { value: "这是受控组件默认值" };
  }
  render() {
    return <div>{this.state.value}</div>;
  }
}

不受控组件:组件不被 React的状态值控制,通过 dom 的特性或者React 的ref 来控制

class NoControll extends React.Component {
  render() {
    return <div>{this.props.value}</div>;
  }
}

导入代码:

export default class Sixteen extends React.Component {
  componentDidMount() {
    console.log("ref 获取的不受控组件值为", this.refs["noControll"]);
  }
  render() {
    return (
      <div>
        <Controll></Controll>
        <NoControll
          value={"这是不受控组件传入值"}
          ref="noControll"
        ></NoControll>
      </div>
    );
  }
}

17.高阶组件(HOC)

17.1 定义

1.就是类似高阶函数的定义,将组件作为参数或者返回一个组件的组件;
2.作用:
抽取重复代码,实现组件复用,常见场景,页面复用;
条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景,权限控制;
捕获/劫持被处理组件的生命周期,常见场景,组件渲染性能追踪、日志打点

17.2 实现方法

1.属性代理

import React,{Component} from 'react';

const Seventeen = WrappedComponent =>
  class extends React.Component {
    render() {
      const props = {
        ...this.props,
        name: "这是高阶组件"
      };
      return <WrappedComponent {...props} />;
    }
  };

class WrappedComponent extends React.Component {
  state={
     baseName:'这是基础组件' 
  }
  render() {
    const {baseName} = this.state
    const {name} = this.props
    return <div>
        <div>基础组件值为{baseName}</div>
        <div>通过高阶组件属性代理的得到的值为{name}</div>
    </div>
  }
}

export default Seventeen(WrappedComponent)

2.反向继承
原理就是利用 super 改变改组件的 this 方向,继而就可以在该组件处理容器组件的一些值

const Seventeen = (WrappedComponent)=>{
    return class extends WrappedComponent {
        componentDidMount() {
            this.setState({baseName:'这是通过反向继承修改后的基础组件名称'})
        }
        render(){
            return super.render();
        }
    }
}

class WrappedComponent extends React.Component {
  state={
     baseName:'这是基础组件' 
  }
  render() {
    const {baseName} = this.state
    return <div>
        <div>基础组件值为{baseName}</div>
    </div>
  }
}

export default Seventeen(WrappedComponent);

18.元素是否显示

一般用三元表达式

flag?<div>显示内容</div>:''
 flag&&<div>显示内容</div>

19.Dialog 组件创建

Dialog 应该是用的比较多的组件,下面有三种不同的创建方法 方式 1:通过 state 控制组件是否显示

class NineteenChildOne extends React.Component {
  render() {
    const Dialog = () => <div>这是弹层1</div>;

    return this.props.dialogOneFlag && <Dialog />;
  }
}

方式 2:通过ReactDom.render创建弹层-挂载根节点外层
通过原生的createElement,appendChild, removeChild和react 的ReactDOM.render,ReactDOM.unmountComponentAtNode来控制元素的显示和隐藏

NineteenChild.jsx

import ReactDOM from "react-dom";

class Dialog {
  constructor(name) {
    this.div = document.createElement("div");
    this.div.style.width = "200px";
    this.div.style.height = "200px";
    this.div.style.backgroundColor = "green";
    this.div.style.position = "absolute";
    this.div.style.top = "200px";
    this.div.style.left = "400px";
    this.div.id = "dialog-box";
  }
  show(children) {
    // 销毁
    const dom = document.querySelector("#dialog-box");
    if(!dom){ //兼容多次点击
      // 显示
      document.body.appendChild(this.div);
      ReactDOM.render(children, this.div);
    }
  }
  destroy() {
    // 销毁
    const dom = document.querySelector("#dialog-box");
    if(dom){//兼容多次点击
      ReactDOM.unmountComponentAtNode(this.div);
      dom.parentNode.removeChild(dom);
    }
  }
}
export default {
  show: function(children) {
    new Dialog().show(children);
  },
  hide: function() {
    new Dialog().destroy();
  }
};

nineteen.jsx

twoSubmit=()=>{
    Dialog.show('这是弹层2')
  }

  twoCancel=()=>{
    Dialog.hide()
  }

20.React.memo

作用:当类组件的输入属性相同时,可以使用 pureComponent 或 shouldComponentUpdate 来避免组件的渲染。现在,你可以通过把函数组件包装在 React.memo 中来实现相同的功能

import React from "react";

function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
  if (prevProps.val === nextProps.val) {
    return true;
  } else {
    return false;
  }
}

// React.memo()两个参数,第一个是纯函数,第二个是比较函数
export default React.memo(function twentyChild(props) {
  console.log("MemoSon rendered : " + Date.now());
  return <div>{props.val}</div>;
}, areEqual);

21.React.PureComponent

作用:
1.React.PureComponent 和 React.Component类似,都是定义一个组件类。
2.不同是React.Component没有实现shouldComponentUpdate(),而 React.PureComponent通过props和state的浅比较实现了。
3.React.PureComponent是作用在类中,而React.memo是作用在函数中。
4.如果组件的props和state相同时,render的内容也一致,那么就可以使用React.PureComponent了,这样可以提高组件的性能

class TwentyOneChild extends React.PureComponent{  //组件直接继承React.PureComponent
  render() {
    return <div>{this.props.name}</div>
  }
}

export default class TwentyOne extends React.Component{
    render(){
        return (
            <div>
              <TwentyOneChild name={'这是React.PureComponent的使用方法'}></TwentyOneChild>
            </div>
        )
    }
}

22.React.Component

作用:是基于ES6 class的React组件,React允许定义一个class或者function作为组件,那么定义一个组件类,就需要继承React.Component

export default class TwentyTwo extends React.Component{ //组件定义方法
    render(){
        return (
            <div>这是技巧22</div>
        )
    }
}

23.在 JSX 打印 falsy 值

定义:
1.falsy 值 (虚值) 是在 Boolean 上下文中认定为 false 的值;
2.值有 0,"",'',``,null,undefined,NaN

export default class TwentyThree extends React.Component{
    state={myVariable:null}
    render(){
        return (
            <div>{String(this.state.myVariable)}</div>
        )
    }
}

虚值如果直接展示,会发生隐式转换,为 false,所以页面不显示

24.ReactDOM.createPortal

作用:组件的render函数返回的元素会被挂载在它的父级组件上,createPortal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案

import React from "react";
import ReactDOM from "react-dom";
import {Button} from "antd"

const modalRoot = document.body;

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement("div");
    this.el.style.width = "200px";
    this.el.style.height = "200px";
    this.el.style.backgroundColor = "green";
    this.el.style.position = "absolute";
    this.el.style.top = "200px";
    this.el.style.left = "400px";
  }

  componentDidMount() {
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    modalRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(this.props.children, this.el);
  }
}

function Child() {
  return (
    <div className="modal">
      这个是通过ReactDOM.createPortal创建的内容
    </div>
  );
}

export default class TwentyFour extends React.Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(prevState => ({
      clicks: prevState.clicks + 1
    }));
  }

  render() {
    return (
      <div>
          <Button onClick={this.handleClick}>点击加1</Button>
        <p>点击次数: {this.state.clicks}</p>
        <Modal>
          <Child />
        </Modal>
      </div>
    );
  }
}

这样元素就追加到指定的元素下面啦

25.在 React 使用innerHTML

场景:有些后台返回是 html 格式字段,就需要用到 innerHTML 属性

export default class TwentyFive extends React.Component {
  render() {
    return (
      <div dangerouslySetInnerHTML={{ __html: "<span>这是渲染的 HTML 内容</span>" }}></div>
    );
  }
}

26.React.createElement

语法:
React.createElement( type, [props], [...children] )

源码:

export default class TwentySix extends React.Component {
  render() {
    return (
      <div>
        {React.createElement(
          "div",
          { id: "one", className: "two" },
          React.createElement("span", { id: "spanOne" }, "这是第一个 span 标签"),
          React.createElement("br"),
          React.createElement("span", { id: "spanTwo" }, "这是第二个 span 标签")
        )}
      </div>
    );
  }
}

原理:实质上 JSX 的 dom 最后转化为 js 都是React.createElement

// jsx 语法
<div id='one' class='two'>
    <span id="spanOne">this is spanOne</span>
    <span id="spanTwo">this is spanTwo</span>
</div>

// 转化为 js
React.createElement(
  "div",
 { id: "one", class: "two" },
 React.createElement( "span", { id: "spanOne" }, "this is spanOne"), 
 React.createElement("span", { id: "spanTwo" }, "this is spanTwo")
);

27.React.cloneElement

语法:

React.cloneElement(
  element,
  [props],
  [...children]
)

作用:这个方法的作用是复制组件,给组件传值或者添加属性
核心代码

React.Children.map(children, child => {
  return React.cloneElement(child, {
    count: _this.state.count
  });
});

28.React.Fragment

作用:React.Fragment可以让你聚合一个子元素列表,并且不在DOM中增加额外节点
核心代码

render() {
    const { info } = this.state;
    return (
      <div>
        {info.map((item, index) => {
          return (
            <React.Fragment key={index}>
              <div>{item.name}</div>
              <div>{item.age}</div>
            </React.Fragment>
          );
        })}
      </div>
    );
  }

29.循环元素

内部没有封装像 vue 里面 v-for 的指令,而是通过 map 遍历

{arr.map((item,index)=>{
  return(
    <div key={item.id}>
      <span>{item.name}</span>
      <span>{item.age}</span>
    </div>
  )
})}

30.给 DOM 设置和获取自定义属性

作用:有些要通过自定义属性传值

export default class Thirty extends React.Component {
  click = e => {
    console.log(e.target.getAttribute("data-row"));
  };

  render() {
    return (
      <div>
        <div data-row={"属性1"} data-col={"属性 2"} onClick={this.click}>
          点击获取属性
        </div>
      </div>
    );
  }
}

31.绑定事件

场景:交互就会涉及到事件点击,然后点击选中值传参也是一个很常见场景

import React from "react";
import { Button } from 'antd'

export default class Three extends React.Component {
  state = {
    flag: true,
    flagOne: 1
  };
  click(data1,data2){
    console.log('data1 值为',data1)
    console.log('data2 值为',data2)
  }
  render() {
    return (
      <div>
        <Button type="primary" onClick={this.click.bind(this,'参数 1','参数 2')}>点击事件</Button>
      </div>
    );
  }
}

使用方法在源码 routes.js 有详细使用

32.React-Router

32.1 V3和 V4的区别

1.V3或者说V早期版本是把router 和 layout components 分开;
2.V4是集中式 router,通过 Route 嵌套,实现 Layout 和 page 嵌套,Layout 和 page 组件 是作为 router 的一部分;
3.在V3 中的 routing 规则是 exclusive,意思就是最终只获取一个 route;
4.V4 中的 routes 默认是 inclusive 的,这就意味着多个; 可以同时匹配和呈现.如果只想匹配一个路由,可以使用Switch,在 中只有一个 会被渲染,同时可以再在每个路由添加exact,做到精准匹配 Redirect,浏览器重定向,当多有都不匹配的时候,进行匹配

32.2 使用

import { HashRouter as Router, Switch  } from "react-router-dom";

class App extends React.Component{
    render(){
        const authPath = '/login' // 默认未登录的时候返回的页面,可以自行设置
        let authed = this.props.state.authed || localStorage.getItem('authed') // 如果登陆之后可以利用redux修改该值
        return (
            <Router>
                <Switch>
                    {renderRoutes(routes, authed, authPath)}
                </Switch>
            </Router>
        )
    }
}

V4是通过 Route 嵌套,实现 Layout 和 page 嵌套,Switch切换路由的作用

33.样式引入方法

方式 1:import 导入

import './App.css';

方式 2:内联方式

import React from 'react';

const Header = () => {

    const heading = '头部组件'

    return(
        <div style={{backgroundColor:'orange'}}>
            <h1>{heading}</h1>
        </div>
    )
}

或者
import React from 'react';

const footerStyle = {
    width: '100%',
    backgroundColor: 'green',
    padding: '50px',
    font: '30px',
    color: 'white',
    fontWeight: 'bold'
}

export const Footer = () => {
    return(
        <div style={footerStyle}>
            底部组件
        </div>
    )
}

34.动态绑定 className

原理:通过三元表达式控制 className 值

render(){
  const flag=true
  return (
    <div className={flag?"active":"no-active"}>这是技巧 34</div>
  )
}

组件类

组件类,详细分的话有三种类,第一类说白了就是我平时用于继承的基类组件Component,PureComponent,还有就是react提供的内置的组件,比如Fragment,StrictMode,另一部分就是高阶组件forwardRef,memo等。

comp.jpg

Component

Componentclass组件的根基。类组件一切始于Component。对于React.Component使用,我们没有什么好讲的。我们这里重点研究一下reactComponent做了些什么。

react/src/ReactBaseClasses.js
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

这就是Component函数,其中updater对象上保存着更新组件的方法。

我们声明的类组件是什么时候以何种形式被实例化的呢?

react-reconciler/src/ReactFiberClassComponent.js

constructClassInstance

function constructClassInstance(
    workInProgress,
    ctor,
    props
){
   const instance = new ctor(props, context);
    instance.updater = {
        isMounted,
        enqueueSetState(){
            /* setState 触发这里面的逻辑 */
        },
        enqueueReplaceState(){},
        enqueueForceUpdate(){
            /* forceUpdate 触发这里的逻辑 */
        }
    }
}

对于Componentreact 处理逻辑还是很简单的,实例化我们类组件,然后赋值updater对象,负责组件的更新。然后在组件各个阶段,执行类组件的render函数,和对应的生命周期函数就可以了。

⭐️PureComponent

PureComponentComponent用法,差不多一样,唯一不同的是,纯组件PureComponent会浅比较,propsstate是否相同,来决定是否重新渲染组件。所以一般用于性能调优,减少render次数。

什么叫做浅比较,我这里举个列子:

class Index extends React.PureComponent{
    constructor(props){
        super(props)
        this.state={
           data:{
              name:'alien',
              age:28
           }
        }
    }
    handerClick= () =>{
        const { data } = this.state
        data.age++
        this.setState({ data })
    }
    render(){
        const { data } = this.state
        return <div className="box" >
        <div className="show" >
            <div> 你的姓名是: { data.name } </div>
            <div> 年龄: { data.age  }</div>
            <button onClick={ this.handerClick } >age++</button>
        </div>
    </div>
    }
}

pureComponent.gif

点击按钮,没有任何反应,因为PureComponent会比较两次data对象,都指向同一个data,没有发生改变,所以不更新视图。

解决这个问题很简单,只需要在handerClick事件中这么写:

this.setState({ data:{...data} })

浅拷贝就能根本解决问题。

memo

React.memoPureComponent作用类似,可以用作性能优化,React.memo 是高阶组件,函数组件和类组件都可以使用, 和区别PureComponentReact.memo只能对props的情况确定是否渲染,而PureComponent是针对propsstate

React.memo 接受两个参数,第一个参数原始组件本身,第二个参数,可以根据一次更新中props是否相同决定原始组件是否重新渲染。是一个返回布尔值,true 证明组件无须重新渲染,false证明组件需要重新渲染,这个和类组件中的shouldComponentUpdate()正好相反 。

React.memo: 第二个参数 返回 true 组件不渲染 , 返回 false 组件重新渲染。 shouldComponentUpdate: 返回 true 组件渲染 , 返回 false 组件不渲染。

接下来我们做一个场景,控制组件在仅此一个props数字变量,一定范围渲染。

例子🌰:

控制 props 中的 number

  • 1 只有 number 更改,组件渲染。
  • 2 只有 number 小于 5 ,组件渲染。
function TextMemo(props){
    console.log('子组件渲染')
    if(props)
    return <div>hello,world</div> 
}

const controlIsRender = (pre,next)=>{
   if(pre.number === next.number  ){ // number 不改变 ,不渲染组件
       return true 
   }else if(pre.number !== next.number && next.number > 5 ) { // number 改变 ,但值大于5 , 不渲染组件
       return true
   }else { // 否则渲染组件
       return false
   }
}

const NewTexMemo = memo(TextMemo,controlIsRender)
class Index extends React.Component{
    constructor(props){
        super(props)
        this.state={
            number:1,
            num:1
        }
    }
    render(){
        const { num , number }  = this.state
        return <div>
            <div>
                改变num:当前值 { num }  
                <button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button>
                <button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button>  
            </div>
            <div>
                改变number: 当前值 { number } 
                <button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button>
                <button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button>  
            </div>
            <NewTexMemo num={ num } number={number}  />
        </div>
    }
}

memo.gif

完美达到了效果,React.memo一定程度上,可以等价于组件外部使用shouldComponentUpdate ,用于拦截新老props,确定组件是否更新。

forwardRef

官网对forwardRef的概念和用法很笼统,也没有给定一个具体的案例。很多同学不知道 forwardRef具体怎么用,下面我结合具体例子给大家讲解forwardRef应用场景。

1 转发引入Ref

这个场景实际很简单,比如父组件想获取孙组件,某一个dom元素。这种隔代ref获取引用,就需要forwardRef来助力。

function Son (props){
    const { grandRef } = props
    return <div>
        <div> i am alien </div>
        <span ref={grandRef} >这个是想要获取元素</span>
    </div>
}

class Father extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return <div>
            <Son grandRef={this.props.grandRef}  />
        </div>
    }
}

const NewFather = React.forwardRef((props,ref)=><Father grandRef={ref}  {...props} />  )

class GrandFather extends React.Component{
    constructor(props){
        super(props)
    }
    node = null 
    componentDidMount(){
        console.log(this.node)
    }
    render(){
        return <div>
            <NewFather ref={(node)=> this.node = node } />
        </div>
    }
}

效果

forwaedRef.jpg

react不允许ref通过props传递,因为组件上已经有 ref 这个属性,在组件调和过程中,已经被特殊处理,forwardRef出现就是解决这个问题,把ref转发到自定义的forwardRef定义的属性上,让ref,可以通过props传递。

2 高阶组件转发Ref

一文吃透hoc文章中讲到,由于属性代理的hoc,被包裹一层,所以如果是类组件,是通过ref拿不到原始组件的实例的,不过我们可以通过forWardRef转发ref

function HOC(Component){
  class Wrap extends React.Component{
     render(){
        const { forwardedRef ,...otherprops  } = this.props
        return <Component ref={forwardedRef}  {...otherprops}  />
     }
  }
  return  React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> ) 
}
class Index extends React.Component{
  componentDidMount(){
      console.log(666)
  }
  render(){
    return <div>hello,world</div>
  }
}
const HocIndex =  HOC(Index,true)
export default ()=>{
  const node = useRef(null)
  useEffect(()=>{
     /* 就可以跨层级,捕获到 Index 组件的实例了 */ 
    console.log(node.current.componentDidMount)
  },[])
  return <div><HocIndex ref={node}  /></div>
}

如上,解决了高阶组件引入Ref的问题。

lazy

React.lazy 和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库

React.lazySuspense配合一起用,能够有动态加载组件的效果。React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise ,该 Promise 需要 resolve 一个 default exportReact 组件。

我们模拟一个动态加载的场景。

父组件

import Test from './comTest'
const LazyComponent =  React.lazy(()=> new Promise((resolve)=>{
      setTimeout(()=>{
          resolve({
              default: ()=> <Test />
          })
      },2000)
}))
class index extends React.Component{   
    render(){
        return <div className="context_box"  style={ { marginTop :'50px' } }   >
           <React.Suspense fallback={ <div className="icon" ><SyncOutlined  spin  /></div> } >
               <LazyComponent />
           </React.Suspense>
        </div>
    }
}

我们用setTimeout来模拟import异步引入效果。

Test

class Test extends React.Component{
    constructor(props){
        super(props)
    }
    componentDidMount(){
        console.log('--componentDidMount--')
    }
    render(){
        return <div>
            <img src={alien}  className="alien" />
        </div>
    }
}

效果

lazy.gif

Suspense

何为Suspense, Suspense 让组件“等待”某个异步操作,直到该异步操作结束即可渲染。

用于数据获取的 Suspense 是一个新特性,你可以使用 `` 以声明的方式来“等待”任何内容,包括数据。本文重点介绍它在数据获取的用例,它也可以用于等待图像、脚本或其他异步的操作。

上面讲到高阶组件lazy时候,已经用 lazy + Suspense模式,构建了异步渲染组件。我们看一下官网文档中的案例:

const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懒加载
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>

Fragment

react不允许一个组件返回多个节点元素,比如说如下情况

render(){
    return <li> 🍎🍎🍎 </li>
           <li> 🍌🍌🍌 </li>
           <li> 🍇🍇🍇 </li>
}

如果我们想解决这个情况,很简单,只需要在外层套一个容器元素。

render(){
    return <div>
           <li> 🍎🍎🍎 </li>
           <li> 🍌🍌🍌 </li>
           <li> 🍇🍇🍇 </li>
    </div>
}

但是我们不期望,增加额外的dom节点,所以react提供Fragment碎片概念,能够让一个组件返回多个元素。 所以我们可以这么写

<React.Fragment>
    <li> 🍎🍎🍎 </li>
    <li> 🍌🍌🍌 </li>
    <li> 🍇🍇🍇 </li>
</React.Fragment>

还可以简写成:

<>
    <li> 🍎🍎🍎 </li>
    <li> 🍌🍌🍌 </li>
    <li> 🍇🍇🍇 </li>
</>

Fragment区别是,Fragment可以支持key属性。<>不支持key属性。

温馨提示。我们通过map遍历后的元素,react底层会处理,默认在外部嵌套一个``。

比如:

{
   [1,2,3].map(item=><span key={item.id} >{ item.name }</span>)
}

react底层处理之后,等价于:

<Fragment>
   <span></span>
   <span></span>
   <span></span>
</Fragment>

Profiler

Profiler这个api一般用于开发阶段,性能检测,检测一次react组件渲染用时,性能开销。

Profiler 需要两个参数:

第一个参数:是 id,用于表识唯一性的Profiler

第二个参数:onRender回调函数,用于渲染完成,接受渲染参数。

实践:

const index = () => {
  const callback = (...arg) => console.log(arg)
  return <div >
    <div >
      <Profiler id="root" onRender={ callback }  >
        <Router  >
          <Meuns/>
          <KeepaliveRouterSwitch withoutRoute >
              { renderRoutes(menusList) }
          </KeepaliveRouterSwitch>
        </Router>
      </Profiler> 
    </div>
  </div>
}

结果

Profiler.jpg

onRender

  • 0 -id: root -> Profiler 树的 id

  • 1 -phase: mount -> mount 挂载 , update 渲染了。

  • 2 -actualDuration: 6.685000262223184 -> 更新 committed 花费的渲染时间。

  • 3 -baseDuration: 4.430000321008265 -> 渲染整颗子树需要的时间

  • 4 -startTime : 689.7299999836832 -> 本次更新开始渲染的时间

  • 5 -commitTime : 698.5799999674782 -> 本次更新committed 的时间

  • 6 -interactions: set{} -> 本次更新的 interactions 的集合

尽管 Profiler 是一个轻量级组件,我们依然应该在需要时才去使用它。对一个应用来说,每添加一些都会给 CPU 和内存带来一些负担。

StrictMode

StrictMode见名知意,严格模式,用于检测react项目中的潜在的问题,。与 Fragment 一样, StrictMode 不会渲染任何可见的 UI 。它为其后代元素触发额外的检查和警告。

严格模式检查仅在开发模式下运行;它们不会影响生产构建。

StrictMode目前有助于:

  • ①识别不安全的生命周期。
  • ②关于使用过时字符串 ref API 的警告
  • ③关于使用废弃的 findDOMNode 方法的警告
  • ④检测意外的副作用
  • ⑤检测过时的 context API

实践:识别不安全的生命周期

对于不安全的生命周期,指的是UNSAFE_componentWillMountUNSAFE_componentWillReceiveProps , UNSAFE_componentWillUpdate

外层开启严格模式:

<React.StrictMode> 
    <Router  >
        <Meuns/>
        <KeepaliveRouterSwitch withoutRoute >
            { renderRoutes(menusList) }
        </KeepaliveRouterSwitch>
    </Router>
</React.StrictMode>

我们在内层组件中,使用不安全的生命周期:

class Index extends React.Component{    
    UNSAFE_componentWillReceiveProps(){
    }
    render(){      
        return <div className="box" />   
    }
}

结果

strictMode.jpg

工具类

接下来我们一起来探究一下react工具类函数的用法。

utils.jpg

createElement

一提到createElement,就不由得和JSX联系一起。我们写的jsx,最终会被 babel,用createElement编译成react元素形式。我写一个组件,我们看一下会被编译成什么样子,

如果我们在render里面这么写:

render(){
    return <div className="box" >
        <div className="item"  >生命周期</div>
        <Text  mes="hello,world"  />
        <React.Fragment> Flagment </React.Fragment>
        { /*  */ }
        text文本
    </div>
}

会被编译成这样:

render() {
    return React.createElement("div", { className: "box" },
            React.createElement("div", { className: "item" }, "\u751F\u547D\u5468\u671F"),
            React.createElement(Text, { mes: "hello,world" }),
            React.createElement(React.Fragment, null, " Flagment "),
            "text\u6587\u672C");
    }

当然我们可以不用jsx模式,而是直接通过createElement进行开发。

createElement模型:

React.createElement(
  type,
  [props],
  [...children]
)

createElement参数:

第一个参数: 如果是组件类型,会传入组件,如果是dom元素类型,传入div或者span之类的字符串。

第二个参数: 第二个参数为一个对象,在dom类型中为属性,在组件类型中为props

**其他参数: **依次为children,根据顺序排列。

createElement做了些什么?

经过createElement处理,最终会形成 $$typeof = Symbol(react.element)对象。对象上保存了该react.element的信息。

cloneElement

可能有的同学还傻傻的分不清楚cloneElementcreateElement区别和作用。

createElement把我们写的jsx,变成element对象; 而cloneElement的作用是以 element 元素为样板克隆并返回新的 React 元素。返回元素的 props 是将新的 props 与原始元素的 props 浅层合并后的结果。

那么cloneElement感觉在我们实际业务组件中,可能没什么用,但是在一些开源项目,或者是公共插槽组件中用处还是蛮大的,比如说,我们可以在组件中,劫持children element,然后通过cloneElement克隆element,混入props。经典的案例就是 react-router中的Swtich组件,通过这种方式,来匹配唯一的 Route并加以渲染。

我们设置一个场景,在组件中,去劫持children,然后给children赋能一些额外的props:

function FatherComponent({ children }){
    const newChildren = React.cloneElement(children, { age: 18})
    return <div> { newChildren } </div>
}

function SonComponent(props){
    console.log(props)
    return <div>hello,world</div>
}

class Index extends React.Component{    
    render(){      
        return <div className="box" >
            <FatherComponent>
                <SonComponent name="alien"  />
            </FatherComponent>
        </div>   
    }
}

打印:

cloneElment.jpg

完美达到了效果!

createContext

createContext用于创建一个Context对象,createContext对象中,包括用于传递 Context 对象值 valueProvider,和接受value变化订阅的Consumer

const MyContext = React.createContext(defaultValue)

createContext接受一个参数defaultValue,如果Consumer上一级一直没有Provider,则会应用defaultValue作为value只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

我们来模拟一个 Context.ProviderContext.Consumer的例子:

function ComponentB(){
    /* 用 Consumer 订阅, 来自 Provider 中 value 的改变  */
    return <MyContext.Consumer>
        { (value) => <ComponentA  {...value} /> }
    </MyContext.Consumer>
}

function ComponentA(props){
    const { name , mes } = props
    return <div> 
            <div> 姓名: { name }  </div>
            <div> 想对大家说: { mes }  </div>
         </div>
}

function index(){
    const [ value , ] = React.useState({
        name:'alien',
        mes:'let us learn React '
    })
    return <div style={{ marginTop:'50px' }} >
        <MyContext.Provider value={value}  >
          <ComponentB />
    </MyContext.Provider>
    </div>
}

打印结果:

createContent.jpg

ProviderConsumer的良好的特性,可以做数据的Consumer一方面传递value,另一方面可以订阅value的改变。

Provider还有一个特性可以层层传递value,这种特性在react-redux中表现的淋漓尽致。

createFactory

React.createFactory(type)

返回用于生成指定类型 React 元素的函数。类型参数既可以是标签名字符串(像是 'div' 或 'span'),也可以是 React 组件 类型 ( class 组件或函数组件),或是 React fragment 类型。

使用:

const Text = React.createFactory(()=><div>hello,world</div>) 
function Index(){  
    return <div style={{ marginTop:'50px'  }} >
        <Text/>
    </div>
}

效果

createFactory.jpg

报出警告,这个api将要被废弃,我们这里就不多讲了,如果想要达到同样的效果,请用React.createElement

createRef

createRef可以创建一个 ref 元素,附加在react元素上。

用法:

class Index extends React.Component{
    constructor(props){
        super(props)
        this.node = React.createRef()
    }
    componentDidMount(){
        console.log(this.node)
    }
    render(){
        return <div ref={this.node} > my name is alien </div>
    }
}

个人觉得createRef这个方法,很鸡肋,我们完全可以class类组件中这么写,来捕获ref

class Index extends React.Component{
    node = null
    componentDidMount(){
        console.log(this.node)
    }
    render(){
        return <div ref={(node)=> this.node } > my name is alien </div>
    }
}

或者在function组件中这么写:

function Index(){
    const node = React.useRef(null)
    useEffect(()=>{
        console.log(node.current)
    },[])
    return <div ref={node} >  my name is alien </div>
}

isValidElement

这个方法可以用来检测是否为react element元素,接受待验证对象,返回true或者false。这个api可能对于业务组件的开发,作用不大,因为对于组件内部状态,都是已知的,我们根本就不需要去验证,是否是react element 元素。 但是,对于一起公共组件或是开源库,isValidElement就很有作用了。

实践

我们做一个场景,验证容器组件的所有子组件,过滤到非react element类型。

没有用isValidElement验证之前:

const Text = () => <div>hello,world</div> 
class WrapComponent extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return this.props.children
    }
}
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            <Text/>
            <div> my name is alien </div>
            Let's learn react together!
        </WrapComponent>
    </div>
}

过滤之前的效果

isValidElement.jpg

我们用isValidElement进行react element验证:

class WrapComponent extends React.Component{
    constructor(props){
        super(props)
        this.newChidren = this.props.children.filter(item => React.isValidElement(item) )
    }
    render(){
        return this.newChidren
    }
}

过滤之后效果

isValidElement111.jpg

过滤掉了非react elementLet's learn react together!

Children.map

接下来的五个api都是和react.Chidren相关的,我们来分别介绍一下,我们先来看看官网的描述,React.Children 提供了用于处理 this.props.children 不透明数据结构的实用方法。

有的同学会问遍历 children用数组方法,mapforEach 不就可以了吗? 请我们注意一下不透明数据结构,什么叫做不透明结构?

我们先看一下透明的结构:

class Text extends React.Component{
    render(){
        return <div>hello,world</div>
    }
}
function WrapComponent(props){
    console.log(props.children)
    return props.children
}
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            <Text/>
            <Text/>
            <Text/>
            <span>hello,world</span>
        </WrapComponent>
    </div>
}

打印

chidrenmap.jpg

但是我们把Index结构改变一下:

function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            { new Array(3).fill(0).map(()=><Text/>) }
            <span>hello,world</span>
        </WrapComponent>
    </div>
}

打印

chidrenmap2.jpg

这个数据结构,我们不能正常的遍历了,即使遍历也不能遍历,每一个子元素。此时就需要 react.Chidren 来帮忙了。

但是我们把WrapComponent组件用react.Chidren处理children:

function WrapComponent(props){
    const newChildren = React.Children.map(props.children,(item)=>item)
    console.log(newChildren)
    return newChildren
}

此时就能正常遍历了,达到了预期效果。

C71364B2-25E8-4F7D-A26D-50CA36AF4E33.jpg

注意 如果 children 是一个 Fragment 对象,它将被视为单一子节点的情况处理,而不会被遍历。

Children.forEach

Children.forEachChildren.map 用法类似,Children.map可以返回新的数组,Children.forEach仅停留在遍历阶段。

我们将上面的WrapComponent方法,用Children.forEach改一下。

function WrapComponent(props){
    React.Children.forEach(props.children,(item)=>console.log(item))
    return props.children
}

Children.count

children 中的组件总数量,等同于通过 mapforEach 调用回调函数的次数。对于更复杂的结果,Children.count可以返回同一级别子组件的数量。

我们还是把上述例子进行改造:

function WrapComponent(props){
    const childrenCount =  React.Children.count(props.children)
    console.log(childrenCount,'childrenCount')
    return props.children
}   
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            { new Array(3).fill(0).map((item,index) => new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) }
            <span>hello,world</span>
        </WrapComponent>
    </div>
}

效果:

chidrencunt.jpg

Children.toArray

Children.toArray返回,props.children扁平化后结果。

function WrapComponent(props){
    const newChidrenArray =  React.Children.toArray(props.children)
    console.log(newChidrenArray,'newChidrenArray')
    return newChidrenArray
}   
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            { new Array(3).fill(0).map((item,index)=>new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) }
            <span>hello,world</span>
        </WrapComponent>
    </div>
}

效果:

chuldeanarrgy.jpg

newChidrenArray ,就是扁平化的数组结构。React.Children.toArray() 在拉平展开子节点列表时,更改 key 值以保留嵌套数组的语义。也就是说, toArray 会为返回数组中的每个 key 添加前缀,以使得每个元素 key 的范围都限定在此函数入参数组的对象内。

Children.only

验证 children 是否只有一个子节点(一个 React 元素),如果有则返回它,否则此方法会抛出错误。

不唯一

function WrapComponent(props){
    console.log(React.Children.only(props.children))
    return props.children
}   
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
            { new Array(3).fill(0).map((item,index)=><Text key={index} />) }
            <span>hello,world</span>
        </WrapComponent>
    </div>
}

效果

falseonly.jpg

唯一

function WrapComponent(props){
    console.log(React.Children.only(props.children))
    return props.children
}   
function Index(){
    return <div style={{ marginTop:'50px' }} >
        <WrapComponent>
           <Text/>
        </WrapComponent>
    </div>
}

效果

only.jpg

React.Children.only() 不接受 React.Children.map() 的返回值,因为它是一个数组而并不是 React 元素。

react-hooks

对于react-hooks,我已经写了三部曲,介绍了react-hooks使用,自定义hooks,以及react-hooks原理,感兴趣的同学可以去看看,文章末尾有链接,对于常用的api,我这里参考了react-hooks如何使用那篇文章。并做了相应精简化和一些内容的补充。

hooks.jpg

useState

useState可以弥补函数组件没有state的缺陷。useState可以接受一个初识值,也可以是一个函数actionaction返回值作为新的state。返回一个数组,第一个值为state读取值,第二个值为改变statedispatchAction函数。

我们看一个例子:

const DemoState = (props) => {
   /* number为此时state读取值 ,setNumber为派发更新的函数 */
   let [number, setNumber] = useState(0) /* 0为初始值 */
   return (<div>
       <span>{ number }</span>
       <button onClick={ ()=> {
         setNumber(number+1) /* 写法一 */
         setNumber(number=>number + 1 ) /* 写法二 */
         console.log(number) /* 这里的number是不能够即时改变的  */
       } } >num++</button>
   </div>)
}

useEffect

useEffect可以弥补函数组件没有生命周期的缺点。我们可以在useEffect第一个参数回调函数中,做一些请求数据,事件监听等操作,第二个参数作为dep依赖项,当依赖项发生变化,重新执行第一个函数。

useEffect可以用作数据交互。

/* 模拟数据交互 */
function getUserInfo(a){
    return new Promise((resolve)=>{
        setTimeout(()=>{ 
           resolve({
               name:a,
               age:16,
           }) 
        },500)
    })
}
const DemoEffect = ({ a }) => {
    const [ userMessage , setUserMessage ] :any= useState({})
    const div= useRef()
    const [number, setNumber] = useState(0)
    /* 模拟事件监听处理函数 */
    const handleResize =()=>{}
    /* useEffect使用 ,这里如果不加限制 ,会是函数重复执行,陷入死循环*/
    useEffect(()=>{
        /* 请求数据 */
       getUserInfo(a).then(res=>{
           setUserMessage(res)
       })
       /* 操作dom  */
       console.log(div.current) /* div */
       /* 事件监听等 */
        window.addEventListener('resize', handleResize)
    /* 只有当props->a和state->number改变的时候 ,useEffect副作用函数重新执行 ,如果此时数组为空[],证明函数只有在初始化的时候执行一次相当于componentDidMount */
    },[ a ,number ])
    return (<div ref={div} >
        <span>{ userMessage.name }</span>
        <span>{ userMessage.age }</span>
        <div onClick={ ()=> setNumber(1) } >{ number }</div>
    </div>)
}

useEffect可以用作事件监听,还有一些基于dom的操作。,别忘了在useEffect第一个参数回调函数,返一个函数用于清除事件监听等操作。

const DemoEffect = ({ a }) => {
    /* 模拟事件监听处理函数 */
    const handleResize =()=>{}
    useEffect(()=>{
       /* 定时器 延时器等 */
       const timer = setInterval(()=>console.log(666),1000)
       /* 事件监听 */
       window.addEventListener('resize', handleResize)
       /* 此函数用于清除副作用 */
       return function(){
           clearInterval(timer) 
           window.removeEventListener('resize', handleResize)
       }
    },[ a ])
    return (<div  >
    </div>)
}

useMemo

useMemo接受两个参数,第一个参数是一个函数,返回值用于产生保存值。 第二个参数是一个数组,作为dep依赖项,数组里面的依赖项发生变化,重新执行第一个函数,产生新的值

应用场景: 1 缓存一些值,避免重新执行上下文

const number = useMemo(()=>{
    /** ....大量的逻辑运算 **/
   return number
},[ props.number ]) // 只有 props.number 改变的时候,重新计算number的值。

2 减少不必要的dom循环

/* 用 useMemo包裹的list可以限定当且仅当list改变的时候才更新此list,这样就可以避免selectList重新循环 */
 {useMemo(() => (
      <div>{
          selectList.map((i, v) => (
              <span
                  className={style.listSpan}
                  key={v} >
                  {i.patentName} 
              </span>
          ))}
      </div>
), [selectList])}

3 减少子组件渲染

/* 只有当props中,list列表改变的时候,子组件才渲染 */
const  goodListChild = useMemo(()=> <GoodList list={ props.list } /> ,[ props.list ])

useCallback

useMemouseCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于 useMemo 返回的是函数运行的结果, useCallback 返回的是函数。 返回的callback可以作为props回调函数传递给子组件。

/* 用react.memo */
const DemoChildren = React.memo((props)=>{
   /* 只有初始化的时候打印了 子组件更新 */
    console.log('子组件更新')
   useEffect(()=>{
       props.getInfo('子组件')
   },[])
   return <div>子组件</div>
})
const DemoUseCallback=({ id })=>{
    const [number, setNumber] = useState(1)
    /* 此时usecallback的第一参数 (sonName)=>{ console.log(sonName) }
     经过处理赋值给 getInfo */
    const getInfo  = useCallback((sonName)=>{
          console.log(sonName)
    },[id])
    return <div>
        {/* 点击按钮触发父组件更新 ,但是子组件没有更新 */}
        <button onClick={ ()=>setNumber(number+1) } >增加</button>
        <DemoChildren getInfo={getInfo} />
    </div>
}

useRef

useRef的作用:

  • 一 是可以用来获取dom元素,或者class组件实例 。
  • react-hooks原理文章中讲过,创建useRef时候,会创建一个原始对象,只要函数组件不被销毁,原始对象就会一直存在,那么我们可以利用这个特性,来通过useRef保存一些数据。
const DemoUseRef = ()=>{
    const dom= useRef(null)
    const handerSubmit = ()=>{
        /*  <div >表单组件</div>  dom 节点 */
        console.log(dom.current)
    }
    return <div>
        {/* ref 标记当前dom节点 */}
        <div ref={dom} >表单组件</div>
        <button onClick={()=>handerSubmit()} >提交</button> 
    </div>
}

useLayoutEffect

useEffect执行顺序: 组件更新挂载完成 -> 浏览器 dom 绘制完成 -> 执行 useEffect 回调。 useLayoutEffect 执行顺序: 组件更新挂载完成 -> 执行 useLayoutEffect 回调-> 浏览器dom绘制完成。

所以说 useLayoutEffect 代码可能会阻塞浏览器的绘制 。我们写的 effectuseLayoutEffectreact在底层会被分别打上PassiveEffectHookLayout,在commit阶段区分出,在什么时机执行。

const DemoUseLayoutEffect = () => {
    const target = useRef()
    useLayoutEffect(() => {
        /*我们需要在dom绘制之前,移动dom到制定位置*/
        const { x ,y } = getPositon() /* 获取要移动的 x,y坐标 */
        animate(target.current,{ x,y })
    }, []);
    return (
        <div >
            <span ref={ target } className="animate"></span>
        </div>
    )
}

useReducer

react-hooks原理那篇文章中讲解到,useState底层就是一个简单版的useReducer

useReducer 接受的第一个参数是一个函数,我们可以认为它就是一个 reducer , reducer 的参数就是常规 reducer 里面的 stateaction ,返回改变后的 state , useReducer 第二个参数为 state 的初始值 返回一个数组,数组的第一项就是更新之后 state 的值 ,第二个参数是派发更新的 dispatch 函数。

我们来看一下useReducer如何使用:

const DemoUseReducer = ()=>{
    /* number为更新后的state值,  dispatchNumbner 为当前的派发函数 */
   const [ number , dispatchNumbner ] = useReducer((state,action)=>{
       const { payload , name  } = action
       /* return的值为新的state */
       switch(name){
           case 'add':
               return state + 1
           case 'sub':
               return state - 1 
           case 'reset':
             return payload       
       }
       return state
   },0)
   return <div>
      当前值:{ number }
      { /* 派发更新 */ }
      <button onClick={()=>dispatchNumbner({ name:'add' })} >增加</button>
      <button onClick={()=>dispatchNumbner({ name:'sub' })} >减少</button>
      <button onClick={()=>dispatchNumbner({ name:'reset' ,payload:666 })} >赋值</button>
      { /* 把dispatch 和 state 传递给子组件  */ }
      <MyChildren  dispatch={ dispatchNumbner } State={{ number }} />
   </div>
}

useContext

我们可以使用 useContext ,来获取父级组件传递过来的 context 值,这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式引入 ,也可以父级上下文 context 传递 ( 参数为 context )。useContext 可以代替 context.Consumer 来获取 Provider 中保存的 value

/* 用useContext方式 */
const DemoContext = ()=> {
    const value:any = useContext(Context)
    /* my name is alien */
return <div> my name is { value.name }</div>
}
/* 用Context.Consumer 方式 */
const DemoContext1 = ()=>{
    return <Context.Consumer>
         {/*  my name is alien  */}
        { (value)=> <div> my name is { value.name }</div> }
    </Context.Consumer>
}

export default ()=>{
    return <div>
        <Context.Provider value={{ name:'alien' , age:18 }} >
            <DemoContext />
            <DemoContext1 />
        </Context.Provider>
    </div>
}

useImperativeHandle

useImperativeHandle 可以配合 forwardRef 自定义暴露给父组件的实例值。这个很有用,我们知道,对于子组件,如果是class类组件,我们可以通过ref获取类组件的实例,但是在子组件是函数组件的情况,如果我们不能直接通过ref的,那么此时useImperativeHandleforwardRef配合就能达到效果。

useImperativeHandle接受三个参数:

  • 第一个参数ref: 接受 forWardRef 传递过来的 ref
  • 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的ref对象。
  • 第三个参数 deps:依赖项 deps,依赖项更改形成新的ref对象。

我们来模拟给场景,用useImperativeHandle,使得父组件能让子组件中的input自动赋值并聚焦。

function Son (props,ref) {
    console.log(props)
    const inputRef = useRef(null)
    const [ inputValue , setInputValue ] = useState('')
    useImperativeHandle(ref,()=>{
       const handleRefs = {
           /* 声明方法用于聚焦input框 */
           onFocus(){
              inputRef.current.focus()
           },
           /* 声明方法用于改变input的值 */
           onChangeValue(value){
               setInputValue(value)
           }
       }
       return handleRefs
    },[])
    return <div>
        <input
            placeholder="请输入内容"
            ref={inputRef}
            value={inputValue}
        />
    </div>
}

const ForwarSon = forwardRef(Son)

class Index extends React.Component{
    inputRef = null
    handerClick(){
       const { onFocus , onChangeValue } =this.cur
       onFocus()
       onChangeValue('let us learn React!')
    }
    render(){
        return <div style={{ marginTop:'50px' }} >
            <ForwarSon ref={node => (this.inputRef = node)} />
            <button onClick={this.handerClick.bind(this)} >操控子组件</button>
        </div>
    }
}

useImperativeHandle.gif

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。这个hooks目的就是检查自定义hooks

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  // ...
  // 在开发者工具中的这个 Hook 旁边显示标签
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

我们不推荐你向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。在某些情况下,格式化值的显示可能是一项开销很大的操作。除非需要检查 Hook,否则没有必要这么做。因此,useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

useTransition

useTransition允许延时由state改变而带来的视图渲染。避免不必要的渲染。它还允许组件将速度较慢的数据获取更新推迟到随后渲染,以便能够立即渲染更重要的更新。

const TIMEOUT_MS = { timeoutMs: 2000 }
const [startTransition, isPending] = useTransition(TIMEOUT_MS)
  • useTransition 接受一个对象, timeoutMs代码需要延时的时间。
  • 返回一个数组。第一个参数: 是一个接受回调的函数。我们用它来告诉 React 需要推迟的 state第二个参数: 一个布尔值。表示是否正在等待,过度状态的完成(延时state的更新)。

下面我们引入官网的列子,来了解useTransition的使用。

const SUSPENSE_CONFIG = { timeoutMs: 2000 };

function App() {
  const [resource, setResource] = useState(initialResource);
  const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
  return (
    <>
      <button
        disabled={isPending}
        onClick={() => {
          startTransition(() => {
            const nextUserId = getNextId(resource.userId);
            setResource(fetchProfileData(nextUserId));
          });
        }}
      >
        Next
      </button>
      {isPending ? " 加载中..." : null}
      <Suspense fallback={<Spinner />}>
        <ProfilePage resource={resource} />
      </Suspense>
    </>
  );
}

在这段代码中,我们使用 startTransition 包装了我们的数据获取。这使我们可以立即开始获取用户资料的数据,同时推迟下一个用户资料页面以及其关联的 Spinner 的渲染 2 秒钟( timeoutMs 中显示的时间)。

Taro使用笔记

CLI 工具安装

# 使用 npm 安装 CLI
$ npm install -g @tarojs/cli

# OR 使用 yarn 安装 CLI
$ yarn global add @tarojs/cli

# OR 安装了 cnpm,使用 cnpm 安装 CLI
$ cnpm install -g @tarojs/cli

如果安装过程出现sass相关的安装错误

npm install -g mirror-config-china

项目初始化

使用命令创建模板项目:

taro init myApp

npm 5.2+ 也可在不全局安装的情况下使用 npx 创建模板项目:

npx @tarojs/cli init myApp

编译

使用 Taro 的 build 命令可以把 Taro 代码编译成不同端的代码,然后在对应的开发工具中查看效果。

Taro 编译分为 devbuild 模式:

  • dev 模式(增加 --watch 参数) 将会监听文件修改。
  • build 模式(去掉 --watch 参数) 将不会监听文件修改,并会对代码进行压缩打包。
  • dev 模式生成的文件较大,设置环境变量 NODE_ENVproduction 可以开启压缩,方便预览,但编译速度会下降。

微信小程序

# yarn
$ yarn dev:weapp
$ yarn build:weapp

# npm script
$ npm run dev:weapp
$ npm run build:weapp

# 仅限全局安装
$ taro build --type weapp --watch
$ taro build --type weapp

# npx 用户也可以使用
$ npx taro build --type weapp --watch
$ npx taro build --type weapp

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type weapp --watch # Windows
$ NODE_ENV=production taro build --type weapp --watch # Mac

下载并打开微信开发者工具,然后选择项目根目录进行预览。

百度小程序

# yarn
$ yarn dev:swan
$ yarn build:swan

# npm script
$ npm run dev:swan
$ npm run build:swan

# 仅限全局安装
$ taro build --type swan --watch
$ taro build --type swan

# npx 用户也可以使用
$ npx taro build --type swan --watch
$ npx taro build --type swan

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type swan --watch # Windows
$ NODE_ENV=production taro build --type swan --watch # Mac

支付宝小程序

# yarn
$ yarn dev:alipay
$ yarn build:alipay

# npm script
$ npm run dev:alipay
$ npm run build:alipay

# 仅限全局安装
$ taro build --type alipay --watch
$ taro build --type alipay

# npx 用户也可以使用
$ npx taro build --type alipay --watch
$ npx taro build --type alipay

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type alipay --watch # Windows
$ NODE_ENV=production taro build --type alipay --watch # Mac

字节跳动小程序

# yarn
$ yarn dev:tt
$ yarn build:tt

# npm script
$ npm run dev:tt
$ npm run build:tt

# 仅限全局安装
$ taro build --type tt --watch
$ taro build --type tt

# npx 用户也可以使用
$ npx taro build --type tt --watch
$ npx taro build --type tt

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type tt --watch # Windows
$ NODE_ENV=production taro build --type tt --watch # Mac

QQ 小程序

# yarn
$ yarn dev:qq
$ yarn build:qq

# npm script
$ npm run dev:qq
$ npm run build:qq

# 仅限全局安装
$ taro build --type qq --watch
$ taro build --type qq

# npx 用户也可以使用
$ npx taro build --type qq --watch
$ npx taro build --type qq

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type qq --watch # Windows
$ NODE_ENV=production taro build --type qq --watch # Mac

京东小程序

# yarn
$ yarn dev:jd
$ yarn build:jd

# npm script
$ npm run dev:jd
$ npm run build:jd

# 仅限全局安装
$ taro build --type jd --watch
$ taro build --type jd

# npx 用户也可以使用
$ npx taro build --type jd --watch
$ npx taro build --type jd

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type jd --watch # Windows
$ NODE_ENV=production taro build --type jd --watch # Mac

企业微信小程序

yarn add @tarojs/plugin-platform-weapp-qy

Taro 项目配置

config = {
  // ...
  plugins: [
    '@tarojs/plugin-platform-weapp-qy'
  ]
}
# yarn
$ yarn dev:qywx
$ yarn build:qywx

# npm script
$ npm run dev:qywx
$ npm run build:qywx

# 仅限全局安装
$ taro build --type qywx --watch
$ taro build --type qywx

# npx 用户也可以使用
$ npx taro build --type qywx --watch
$ npx taro build --type qywx

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type qywx --watch # Windows
$ NODE_ENV=production taro build --type qywx --watch # Mac

钉钉小程序

Taro v3.1+ 开始支持

Taro v3.3.8+: 请使用 @tarojs/plugin-platform-alipay-dd 插件的 ~0.1.0 版本

Taro v3.1 & v3.2: 请使用 @tarojs/plugin-platform-alipay-dd 插件的 ~0.0.5 版本
yarn add @tarojs/plugin-platform-alipay-dd

Taro 项目配置

config = {
  // ...
  plugins: [
    '@tarojs/plugin-platform-alipay-dd'
  ]
}
# yarn
$ yarn dev:dd
$ yarn build:dd

# npm script
$ npm run dev:dd
$ npm run build:dd

# 仅限全局安装
$ taro build --type dd --watch
$ taro build --type dd

# npx 用户也可以使用
$ npx taro build --type dd --watch
$ npx taro build --type dd

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type dd --watch # Windows
$ NODE_ENV=production taro build --type dd --watch # Mac

支付宝 IOT 小程序

安装插件

yarn add @tarojs/plugin-platform-alipay-iot

Taro 项目配置

config = {
  // ...
  plugins: [
    '@tarojs/plugin-platform-alipay-iot'
  ]
}
# yarn
$ yarn dev:iot
$ yarn build:iot

# npm script
$ npm run dev:iot
$ npm run build:iot

# 仅限全局安装
$ taro build --type iot --watch
$ taro build --type iot

# npx 用户也可以使用
$ npx taro build --type iot --watch
$ npx taro build --type iot

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type iot --watch # Windows
$ NODE_ENV=production taro build --type iot --watch # Mac

飞书小程序

yarn add @tarojs/plugin-platform-lark

Taro 项目配置

config = {
  // ...
  plugins: [
    '@tarojs/plugin-platform-lark'
  ]
}

编译

# yarn
$ yarn dev:lark
$ yarn build:lark

# npm script
$ npm run dev:lark
$ npm run build:lark

# 仅限全局安装
$ taro build --type lark --watch
$ taro build --type lark

# npx 用户也可以使用
$ npx taro build --type lark --watch
$ npx taro build --type lark

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type lark --watch # Windows
$ NODE_ENV=production taro build --type lark --watch # Mac

快手小程序

Taro v3.1+ 开始支持

Taro v3.3+: 请使用 taro-plugin-platform-kwai 插件的 1.0 或以上版本

Taro v3.2: 请使用 taro-plugin-platform-ks 插件的 1.2.x 版本

Taro v3.1: 请使用 taro-plugin-platform-ks 插件的 1.0.x 版本

插件安装

yarn add @tarojs/plugin-platform-kwai

Taro 项目配置

config = {
  // ...
  plugins: [
    '@tarojs/plugin-platform-kwai'
  ]
}

编译

# yarn
$ yarn dev:kwai
$ yarn build:kwai

# npm script
$ npm run dev:kwai
$ npm run build:kwai

# 仅限全局安装
$ taro build --type kwai --watch
$ taro build --type kwai

# npx 用户也可以使用
$ npx taro build --type kwai --watch
$ npx taro build --type kwai

# watch 同时开启压缩
$ set NODE_ENV=production && taro build --type kwai --watch # Windows
$ NODE_ENV=production taro build --type kwai --watch # Mac

h5

# yarn
$ yarn dev:h5
$ yarn build:h5

# npm script
$ npm run dev:h5
$ npm run build:h5

# 仅限全局安装
$ taro build --type h5 --watch
$ taro build --type h5

# npx 用户也可以使用
$ npx taro build --type h5 --watch
$ npx taro build --type h5

React native

React Native 端开发流程

由浅入深的实战《教程》

如果发现不一致的情况可以使用 Taro 升级命令 taro update self [版本号] 和 taro update project [版本号]来分别将 CLI 和项目依赖升级到指定版本; 或者也可以手动安装相应版本 CLI,修改 package.json 依赖版本号,然后重装依赖来解决。

# 使用Taro 升级命令更新CLI版本到最新版本
$ taro update self [版本号]
# 使用Taro 升级命令更新CLI版本到指定版本
$ taro update self
# 使用Taro 升级命令将项目依赖升级到与@tarojs/cli一致的版本
$ taro update project 
# 使用Taro 升级命令将项目依赖升级到指定版本
$ taro update project [版本号]

更多资源 https://github.com/NervJS/awesome-taro

https://taro-docs.jd.com/taro/docs/composition

项目目录结构

├── dist                        编译结果目录
|
├── config                      项目编译配置目录
|   ├── index.js                默认配置
|   ├── dev.js                  开发环境配置
|   └── prod.js                 生产环境配置
|
├── src                         源码目录
|   ├── pages                   页面文件目录
|   |   └── index               index 页面目录
|   |       ├── index.js        index 页面逻辑
|   |       ├── index.css       index 页面样式
|   |       └── index.config.js index 页面配置
|   |
|   ├── app.js                  项目入口文件
|   ├── app.css                 项目总通用样式
|   └── app.config.js           项目入口配置
|
├── project.config.json         微信小程序项目配置 project.config.json
├── project.tt.json             字节跳动小程序项目配置 project.config.json
├── project.swan.json           百度小程序项目配置 project.swan.json
├── project.qq.json             QQ 小程序项目配置 project.config.json
|
├── babel.config.js             Babel 配置
├── tsconfig.json               TypeScript 配置
├── .eslintrc                   ESLint 配置
|
└── package.json

编译配置

└── config                      项目编译配置目录
    ├── index.js                默认配置
    ├── dev.js                  开发环境配置
    └── prod.js                 生产环境配置

用于配置 Taro 项目的编译行为、修改 Webpack 配置等,详情请参考编译配置编译配置详情

page

└── src                         源码目录
    └── pages                   页面文件目录
        └── index               index 页面目录
            ├── index.js        index 页面逻辑
            ├── index.css       index 页面样式
            └── index.config.js index 页面配置
  1. 页面配置

page.config.js 对应小程序规范的页面配置文件 page.json,优势在于它是 JS 文件可以编写逻辑。配置以微信小程序的页面配置为规范。详情请参考页面配置

  1. 页面样式

页面的样式文件可以通过 ES6 规范的 import 进行引入。

pages/index/index.js

import './index.css';

Copy

  1. 页面路由

页面路由与小程序规范一致,需要在小程序全局配置 app.config.js 中进行配置。

项目配置

└──project.config.json         微信小程序项目配置 project.config.json

各类小程序平台均有自己的项目配置文件,Taro 支持对它们进行适配,详情请参考项目配置

babel配置

└── babel.config.js             Babel 配置

请参考 Babel 配置

Eslint 配置

└── .eslintrc                   ESLint 配置

ESLint 配置请参考 Github

编译配置

编译配置存放于项目根目录下的 config 目录中,包含三个文件:

  • index.js 是通用配置
  • dev.js 是项目预览时的配置
  • prod.js 是项目打包时的配置

详细的编译配置文档请查阅:编译配置详情

默认配置

const config = {
  // 项目名称
  projectName: 'Awesome Next',
  // 项目创建日期
  date: '2020-6-2',
  // 设计稿尺寸
  designWidth: 750,
  // 设计稿尺寸换算规则
  deviceRatio: {
    640: 2.34 / 2,
    750: 1,
    828: 1.81 / 2
  },
  // 项目源码目录
  sourceRoot: 'src',
  // 项目产出目录
  outputRoot: 'dist',
  // Taro 插件配置
  plugins: [],
  // 全局变量设置
  defineConstants: {},
  // 文件 copy 配置
  copy: {
    patterns: [
    ],
    options: {
    }
  },
  // 框架,react,nerv,vue, vue3 等
  framework: 'react',
  // 小程序端专用配置
  mini: {
    postcss: {
      autoprefixer: {
        enable: true
      },
      // 小程序端样式引用本地资源内联配置
      url: {
        enable: true,
        config: {
          limit: 10240
        }
      },
      cssModules: {
        enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
        config: {
          namingPattern: 'module', // 转换模式,取值为 global/module
          generateScopedName: '[name]__[local]___[hash:base64:5]'
        }
      }
    },
    // 自定义 Webpack 配置
    webpackChain (chain, webpack) {}
  },
  // H5 端专用配置
  h5: {
    publicPath: '/',
    staticDirectory: 'static',
    postcss: {
      autoprefixer: {
        enable: true
      },
      cssModules: {
        enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
        config: {
          namingPattern: 'module', // 转换模式,取值为 global/module
          generateScopedName: '[name]__[local]___[hash:base64:5]'
        }
      }
    },
    // 自定义 Webpack 配置
    webpackChain (chain, webpack) {},
    devServer: {}
  }
};

module.exports = function(merge) {
  if (process.env.NODE_ENV === 'development') {
    return merge({}, config, require('./dev'));
  }
  return merge({}, config, require('./prod'));
};

设计稿及尺寸单位

在 Taro 中尺寸单位建议使用 px百分比 %,Taro 默认会对所有单位进行转换。在 Taro 中书写尺寸按照 1:1 的关系来进行书写,即从设计稿上量的长度 100px,那么尺寸书写就是 100px,当转成微信小程序的时候,尺寸将默认转换为 100rpx,当转成 H5 时将默认转换为以 rem 为单位的值。

如果你希望部分 px 单位不被转换成 rpx 或者 rem ,最简单的做法就是在 px 单位中增加一个大写字母,例如 Px 或者 PX 这样,则会被转换插件忽略。

结合过往的开发经验,Taro 默认以 750px 作为换算尺寸标准,如果设计稿不是以 750px 为标准,则需要在项目配置 config/index.js 中进行设置,例如设计稿尺寸是 640px,则需要修改项目配置 config/index.js 中的 designWidth 配置为 640

/config/index.js

const config = {
  projectName: 'myProject',
  date: '2018-4-18',
  designWidth: 640,
  ....
}

目前 Taro 支持 750640828 三种尺寸设计稿,他们的换算规则如下:

const DEVICE_RATIO = {
  '640': 2.34 / 2,
  '750': 1,
  '828': 1.81 / 2
}

建议使用 Taro 时,设计稿以 iPhone 6 750px 作为设计尺寸标准。

如果你的设计稿是 375 ,不在以上三种之中,那么你需要把 designWidth 配置为 375,同时在 DEVICE_RATIO 中添加换算规则如下:

const DEVICE_RATIO = {
  '640': 2.34 / 2,
  '750': 1,
  '828': 1.81 / 2,
  '375': 2 / 1
}

api

在编译时,Taro 会帮你对样式做尺寸转换操作,但是如果是在 JS 中书写了行内样式,那么编译时就无法做替换了,针对这种情况,Taro 提供了 API Taro.pxTransform 来做运行时的尺寸转换。

Taro.pxTransform(10) // 小程序:rpx,H5:rem

配置

默认配置会对所有的 px 单位进行转换,有大写字母的 PxPX 则会被忽略。

参数默认值如下:

{
  onePxTransform: true, // 设置 1px 是否需要被转换(https://taro-docs.jd.com/taro/docs/size#onepxtransform-boolean)
  unitPrecision: 5, // REM 单位允许的小数位。(https://taro-docs.jd.com/taro/docs/size#unitprecision-number)
  propList: ['*'], // 允许转换的属性。(https://taro-docs.jd.com/taro/docs/size#proplist-array)
  selectorBlackList: [], // 黑名单里的选择器将会被忽略。(https://taro-docs.jd.com/taro/docs/size#selectorblacklist)
  replace: true, // 直接替换而不是追加一条进行覆盖。(https://taro-docs.jd.com/taro/docs/size#replace-boolean)
  mediaQuery: false, // 允许媒体查询里的 px 单位转换 (https://taro-docs.jd.com/taro/docs/size#mediaquery-boolean)
  minPixelValue: 0 // 设置一个可被转换的最小 px 值(https://taro-docs.jd.com/taro/docs/size#minpixelvalue-number)
}

/config/index.js

{
  h5: {
    publicPath: '/',
    staticDirectory: 'static',
    postcss: {
      autoprefixer: {
        enable: true
      },
      pxtransform: {
        enable: true,
        config: {
          selectorBlackList: ['body']
        }
      }
    }
  },
  mini: {
    // ...
    postcss: {
      pxtransform: {
        enable: true,
        config: {
          selectorBlackList: ['body']
        }
      }
    }
  }
}

CSS 编译时忽略(过滤)

当前忽略单个属性的最简单的方法,就是 px 单位使用大写字母

/* `px` is converted to `rem` */
.convert {
  font-size: 16px; // converted to 1rem
}

 /* `Px` or `PX` is ignored by `postcss-pxtorem` but still accepted by browsers */
.ignore {
  border: 1Px solid; // ignored
  border-width: 2PX; // ignored
}

全局配置

根目录下的 app.config.js 文件用来对小程序进行全局配置,配置项遵循微信小程序规范,并且对所有平台进行统一。

注意:

  1. app.config.js 里 require 或 import 引用的 js 文件目前没有经过 Babel 编译语法
  2. 多端差异化逻辑可以使用 process.env.TARO_ENV 变量作条件判断来实现。
  3. app.config.js 不支持多端文件的形式,如 app.weapp.js 这样是不起作用的。

通用配置项

在 H5、React Native、所有小程序均支持的配置。

属性类型必填描述
pagesString Array页面路径列表
windowObject全局的默认窗口表现
tabBarObject底部 tab 栏的表现
subPackagesObject Array分包结构配置

pages

如开发目录为:

├── app.js
├── app.json
├── app.wxss
├── pages
│   │── index
│   │   ├── index.wxml
│   │   ├── index.js
│   │   ├── index.json
│   │   └── index.wxss
│   └── logs
│       ├── logs.wxml
│       └── logs.js
└── utils

app.config.js

export default {
  pages: [
    'pages/index/index',
    'pages/logs/logs'
  ]
}

window

用于设置小程序的状态栏、导航条、标题、窗口背景色,其配置项如下。

属性类型默认值描述
navigationBarBackgroundColorHexColor(十六进制颜色值)#000000导航栏背景颜色,如 #000000
navigationBarTextStyleStringwhite导航栏标题颜色,仅支持 black / white
navigationBarTitleTextString导航栏标题文字内容
navigationStyleStringdefault导航栏样式,仅支持以下值:default 默认样式;custom 自定义导航栏,只保留右上角胶囊按钮
backgroundColorString窗口的背景色
backgroundTextStyleStringdark下拉 loading 的样式,仅支持 dark / light
backgroundColorTopString#ffffff顶部窗口的背景色,仅 iOS 支持
backgroundColorBottomString#ffffff底部窗口的背景色,仅 iOS 支持
enablePullDownRefreshbooleanfalse是否开启当前页面的下拉刷新。
onReachBottomDistanceNumber50页面上拉触底事件触发时距页面底部距离,单位为 px
pageOrientationStringportrait屏幕旋转设置,支持 auto / portrait / landscape 详见 响应显示区域变化

各端支持程度如下#

属性微信百度字节跳动支付宝H5RN
navigationBarBackgroundColor✔️✔️✔️✔️✔️✔️
navigationBarTextStyle✔️✔️✔️✔️✔️
navigationBarTitleText✔️✔️✔️✔️✔️✔️
navigationStyle✔️(微信客户端 6.6.0)✔️(百度 App 版本 11.1.0)✔️
backgroundColor✔️✔️✔️
backgroundTextStyle✔️✔️✔️
backgroundColorTop✔️(微信客户端 6.5.16)✔️
backgroundColorBottom✔️(微信客户端 6.5.16)✔️
enablePullDownRefresh✔️✔️✔️✔️
onReachBottomDistance✔️✔️✔️
pageOrientation✔️ 2.4.0 (auto) / 2.5.0 (landscape)

app.config.js

export default {
  pages: [
    'pages/index/index',
    'pages/logs/logs'
  ],
  window: {
    navigationBarBackgroundColor: '#ffffff',
    navigationBarTextStyle: 'black',
    navigationBarTitleText: '微信接口功能演示',
    backgroundColor: '#eeeeee',
    backgroundTextStyle: 'light'
  }
}

tabBar

如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。

其配置项如下其配置项如下

属性类型必填默认值描述
colorHexColor(十六进制颜色值)tab 上的文字默认颜色,仅支持十六进制颜色
selectedColorHexColor(十六进制颜色值)tab 上的文字选中时的颜色,仅支持十六进制颜色
backgroundColorHexColor(十六进制颜色值)tab 的背景色,仅支持十六进制颜色
borderStyleStringblacktabbar 上边框的颜色, 仅支持 black / white
listArraytab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab
positionStringbottomtabBar的位置,仅支持 bottom / top
customBooleanfalse自定义 tabBar

其中 list 接受一个数组,只能配置最少 2 个、最多 5 个 tab。tab 按数组的顺序排序,每个项都是一个对象,其属性值如下:

属性类型必填描述
pagePathString页面路径,必须在 pages 中先定义
textStringtab 上按钮文字
iconPathString图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。
selectedIconPathString选中时的图片路径,icon 大小限制为40kb,建议尺寸为 81px * 81px,不支持网络图片。 当 position 为 top 时,不显示 icon。

各端支持程度如下#

属性微信百度字节跳动支付宝H5RN
color✔️✔️✔️✔️✔️✔️
selectedColor✔️✔️✔️✔️✔️✔️
backgroundColor✔️✔️✔️✔️✔️✔️
borderStyle✔️✔️✔️✔️✔️
list✔️✔️✔️✔️✔️✔️
position✔️✔️
custom✔️(基础库 2.5.0 以上)

subPackages#

H5 和 RN 会把 subPackages 合入 pages

启用分包加载时,声明项目分包结构

小程序端特有属性#

只在部分小程序中支持的属性,H5、RN 均不支持。

属性类型描述
networkTimeoutObject网络超时时间
debugBoolean是否开启 debug 模式,默认关闭
permissionObject小程序接口权限相关设置
requiredBackgroundModesString Array需要在后台使用的能力,如「音乐播放」
preloadRuleObject分包预下载规则
entryPagePathString小程序默认启动首页
workersStringWorker 代码放置的目录
navigateToMiniProgramAppIdListString Array需要跳转的小程序列表,详见 wx.navigateToMiniProgram

networkTimeout#

各类网络请求的超时时间,单位均为毫秒。

属性类型必填默认值描述
requestNumber60000Taro.request 的超时时间,单位:毫秒
connectSocketNumber60000Taro.connectSocket 的超时时间,单位:毫秒
uploadFileNumber60000Taro.uploadFile 的超时时间,单位:毫秒
downloadFileNumber60000Taro.downloadFile 的超时时间,单位:毫秒

permission

小程序接口权限相关设置。字段类型为 Object,结构为:

属性类型必填默认值描述
scope.userLocationPermissionObject位置相关权限声明

PermissionObject 结构:

属性类型必填默认值描述
descstring小程序获取权限时展示的接口用途说明。最长 30 个字符

app.config.js

export default {
  pages: [
    'pages/index/index',
    'pages/logs/logs'
  ],
  permission: {
    'scope.userLocation': {
      desc: '你的位置信息将用于小程序位置接口的效果展示'
    }
  }
}

图片

requiredBackgroundModes

申明需要后台运行的能力,类型为数组。目前支持以下项目:

  • audio: 后台音乐播放
  • location: 后台定位

app.config.js

export default {
  "pages": ["pages/index/index"],
  "requiredBackgroundModes": ["audio", "location"]
}

微信小程序特有属性

只在微信小程序中支持的属性。

属性类型描述
functionalPagesBoolean是否启用插件功能页,默认关闭
pluginsObject使用到的插件
resizableBooleaniPad 小程序是否支持屏幕旋转,默认关闭
usingComponentsObject全局自定义组件配置
sitemapLocationString指明 sitemap.json 的位置
styleString指定使用升级后的weui样式
useExtendedLibObject指定需要引用的扩展库
entranceDeclareObject微信消息用小程序打开
darkmodeboolean小程序支持 DarkMode
themeLocationString指明 theme.json 的位置
lazyCodeLoadingString配置自定义组件代码按需注入
singlePageObject单页模式相关配置

百度小程序特有属性

只在百度小程序中支持的属性。

属性类型描述
routesArray Object小程序自定义路由相关设置
dynamicLibObject引入动态库
export default {
  "pages": [
      "pages/home/home",
      "pages/list/list",
      "pages/detail/detail"
  ],
  "subPackage": [
      {
          "root": "packageA",
          "pages": [
              "pages/home/home",
              "pages/list/list",
              "pages/detail/detail"
          ]
      }
  ],
  "routes": [
      {
          // 投放入口,scheme中的path
          "path": "home", 
          // 真实的物理存储路径
          "page": "pages/home/home" 
      },
      {
          "path": "list",
          "page": "pages/list/list"
      },
      {
          "path": "foo/bar",
          "page": "pages/list/list"
      }
  ]
}

QQ小程序特有属性

只在 QQ 小程序中支持的属性。

属性类型描述
groupIdListString Object需要打开群资料卡的群号列表

路由功能

Taro 遵循微信小程序的路由规范。只需要修改全局配置的 pages 属性,配置为 Taro 应用中每个页面的路径即可。

路由跳转

可以通过 Taro 提供的 API 来跳转到目的页面。路由 API 的详细用法请查看 API 文档的 导航 章节。

// 跳转到目的页面,打开新页面
Taro.navigateTo({
  url: '/pages/page/path/name'
})

// 跳转到目的页面,在当前页面打开
Taro.redirectTo({
  url: '/pages/page/path/name'
})

路由传参

可以通过在所有跳转的 url 后面添加查询字符串参数进行跳转传参,例如:

// 传入参数 id=2&type=test
Taro.navigateTo({
  url: '/pages/page/path/name?id=2&type=test'
})

获取

import React, { Component } from 'react'
import { View } from '@tarojs/components'

class Index extends Component {
  // 建议在页面初始化时把 getCurrentInstance() 的结果保存下来供后面使用,
  // 而不是频繁地调用此 API
  $instance = getCurrentInstance()

  componentDidMount () {
    // 获取路由参数
    console.log($instance.router.params) // 输出 { id: 2, type: 'test' }
  }

  render () {
    return (
      <View className='index' />
    )
  }
}

export default Index

Taro 案例分析

搭建redux环境

安装

npm install redux react-redux redux-thunk redux-logger

创建文件src/store/index.js

import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '../reducers'

const composeEnhancers =
  typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
      // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
    }) : compose

const middlewares = [
  thunkMiddleware
]

if (process.env.NODE_ENV === 'development') {
  middlewares.push(require('redux-logger').createLogger())
}

const enhancer = composeEnhancers(
  applyMiddleware(...middlewares),
  // other store enhancers if any
)

export default function configStore () {
  const store = createStore(rootReducer, enhancer)
  return store
}

创建src/reducers/index.js

import { combineReducers } from 'redux'
import home from './home'

export default combineReducers({
  home,
})

创建src/reducers/home.js

import {
  HOME_INFO, HOME_SEARCH_COUNT, HOME_RECOMMEND, HOME_PIN
} from '@constants/home'

const INITIAL_STATE = {
  homeInfo: {},
  searchCount: 0,
  pin: [],
  recommend: []
}

export default function home(state = INITIAL_STATE, action) {
  switch(action.type) {
    case HOME_INFO: {
      return {
        ...state,
        homeInfo: action.payload
      }
    }
    case HOME_SEARCH_COUNT: {
      return {
        ...state,
        searchCount: action.payload.count
      }
    }
    case HOME_PIN: {
      // 每3个分成一组
      const pin = []
      action.payload.forEach((item, index) => {
        const groupIndex = parseInt(index / 3)
        if (!pin[groupIndex]) {
          pin[groupIndex] = []
        }
        pin[groupIndex].push(item)
      })
      return { ...state, pin }
    }
    case HOME_RECOMMEND: {
      return {
        ...state,
        recommend: state.recommend.concat(action.payload.rcmdItemList)
      }
    }
    default:
      return state
  }
}

创建src/constants/home.js

export const HOME_INFO = 'HOME_INFO'
export const HOME_SEARCH_COUNT = 'HOME_SEARCH_COUNT'
export const HOME_PIN = 'HOME_PIN'
export const HOME_RECOMMEND = 'HOME_RECOMMEND'

修改src/app.js

import { Component } from 'react'
import './app.scss'
import {Provider} from "react-redux";
import configStore from './store'

const store = configStore()

class App extends Component {

  componentDidMount () {}

  componentDidShow () {}

  componentDidHide () {}

  componentDidCatchError () {}

  // this.props.children 是将要会渲染的页面
  render () {
    return (
      <Provider store={store}>
        {this.props.children}
      </Provider>
    )
    // return this.props.children
  }
}

export default App

举个简单的例子

首先创建src/constants/test.js

test.js

export const type = {
  INCREMENT: 'INCREMENT',
  DECREMENT: 'DECREMENT'
}

创建action

import {type} from "../constants/test";

export function add(){
  return {
    type: type.INCREMENT
  }
}
export function remove(){
  return {
    type: type.DECREMENT
  }
}

创建reducer

import {type} from '../constants/test'

const test = (state={count: 0}, action) => {
  switch (action.type) {
    case type.INCREMENT:
      return {...state,count: state.count + 1}
    case type.DECREMENT:
      return {...state,count: state.count - 1}
    default:
      return state;
  }
}

export default test;
import { combineReducers } from 'redux'
import test from './test'

export default combineReducers({
  test
})

页面中进行测试

import React,{Component} from 'react';
import {Button, View} from "@tarojs/components";
import {connect} from "react-redux";
import  * as actions from "../../../../actions/test";

@connect(state => state.test, {...actions})
class Test extends Component{
  state = {
    count: 0
  }

  handleAdd = () => {
    this.props.add()
  }

  handleRemove = () => {
    this.props.remove()
  }

  render(){
    const { count } = this.props
    return (
      <View>
        {count}
        <Button onClick={this.handleAdd}>+</Button>
        <Button onClick={this.handleRemove}>-</Button>
      </View>
    )
  }
}

export default Test;

搭建基于redux环境的接口请求

创建src/actions/home.js

import {
  HOME_INFO, HOME_SEARCH_COUNT, HOME_RECOMMEND, HOME_PIN
} from '@constants/home'
import {
  API_HOME, API_HOME_SEARCH_COUNT, API_HOME_RECOMMEND, API_HOME_PIN
} from '@constants/api'
import { createAction } from '@utils/redux'

/**
 * 首页数据
 * @param {*} payload
 */
export const dispatchHome = payload => createAction({
  url: API_HOME,
  type: HOME_INFO,
  payload
})

/**
 * 商品总数
 * @param {*} payload
 */
export const dispatchSearchCount = payload => createAction({
  url: API_HOME_SEARCH_COUNT,
  type: HOME_SEARCH_COUNT,
  payload
})

/**
 * 拼团
 * @param {*} payload
 */
export const dispatchPin = payload => createAction({
  url: API_HOME_PIN,
  type: HOME_PIN,
  payload
})

/**
 * 推荐商品
 * @param {*} payload
 */
export const dispatchRecommend = payload => createAction({
  url: API_HOME_RECOMMEND,
  type: HOME_RECOMMEND,
  payload
})

创建src/constants/api.js

/**
 * NOTE HOST、HOST_M 是在 config 中通过 defineConstants 配置的
 * 只所以不在代码中直接引用,是因为 eslint 会报 no-undef 的错误,因此用如下方式处理
 */
/* eslint-disable */
export const host = HOST
export const hostM = HOST_M
/* eslint-enable */

// pic
export const CDN = 'https://yanxuan.nosdn.127.net'

// home
export const API_HOME = `${host}/xhr/index/index.json`
export const API_HOME_SEARCH_COUNT = `${host}/xhr/search/displayBar.json`
export const API_HOME_PIN = `${hostM}/pin/min/item/recommend.json`
export const API_HOME_RECOMMEND = `${host}/xhr/rcmd/index.json`

创建src/utils/redux.js

/**
 * 适当封装 Redux,简化调用
 */
/* eslint-disable import/prefer-default-export */
import fetch from './request'

export function createAction(options) {
  const { url, payload, method, fetchOptions, cb, type } = options
  return (dispatch) => {
    return fetch({ url, payload, method, ...fetchOptions }).then((res) => {
      dispatch({ type, payload: cb ? cb(res) : res })
      return res
    })
  }
}

创建src/utils/request.js

import Taro from '@tarojs/taro'
import { API_USER, API_USER_LOGIN } from '@constants/api'

const CODE_SUCCESS = '200'
const CODE_AUTH_EXPIRED = '600'

function getStorage(key) {
  return Taro.getStorage({ key }).then(res => res.data).catch(() => '')
}

function updateStorage(data = {}) {
  return Promise.all([
    Taro.setStorage({ key: 'token', data: data['3rdSession'] || '' }),
    Taro.setStorage({ key: 'uid', data: data['uid'] || ''})
  ])
}

/**
 * 简易封装网络请求
 * // NOTE 需要注意 RN 不支持 *StorageSync,此处用 async/await 解决
 * @param {*} options
 */
export default async function fetch(options) {
  const { url, payload, method = 'GET', showToast = true, autoLogin = true } = options
  const token = await getStorage('token')
  const header = token ? { 'WX-PIN-SESSION': token, 'X-WX-3RD-Session': token } : {}
  if (method === 'POST') {
    header['content-type'] = 'application/json'
  }

  return Taro.request({
    url,
    method,
    data: payload,
    header
  }).then(async (res) => {
    const { code, data } = res.data
    if (code !== CODE_SUCCESS) {
      if (code === CODE_AUTH_EXPIRED) {
        await updateStorage({})
      }
      return Promise.reject(res.data)
    }

    if (url === API_USER_LOGIN) {
      await updateStorage(data)
    }

    // XXX 用户信息需展示 uid,但是 uid 是登录接口就返回的,比较蛋疼,暂时糅合在 fetch 中解决
    if (url === API_USER) {
      const uid = await getStorage('uid')
      return { ...data, uid }
    }

    return data
  }).catch((err) => {
    const defaultMsg = err.code === CODE_AUTH_EXPIRED ? '登录失效' : '请求异常'
    if (showToast) {
      Taro.showToast({
        title: err && err.errorMsg || defaultMsg,
        icon: 'none'
      })
    }

    if (err.code === CODE_AUTH_EXPIRED && autoLogin) {
      Taro.navigateTo({
        url: '/pages/user-login/user-login'
      })
    }

    return Promise.reject({ message: defaultMsg, ...err })
  })
}

首先在页面中引入action

import * as actions from '@actions/home'

同时引入connect

import {connect} from "react-redux"

使用注解方式进行使用

@connect(state => state.home, { ...actions })

调用方法

this.props.dispatchHome().then(() => {
	this.setState({ loaded: true })
})

Redux基础知识

1 Redux 是什么
  • redux是的诞生是为了给 React 应用提供「可预测化的状态管理」机制。
  • Redux会将整个应用状态(其实也就是数据)存储到到一个地方,称为store
  • 这个store里面保存一棵状态树(state tree)
  • 组件改变state的唯一方法是通过调用store的dispatch(派遣)方法,触发一个action,这个action被对应的reducer处理,于是state完成更新
  • 组件可以派发(dispatch)行为(action)给store,而不是直接通知其它组件
  • 其它组件可以通过订阅store中的状态(state)来刷新自己的视图
  • Redux 专注于状态管理,和react解耦
  • 单一状态,单项数据流
  • 核心概念:store, state, action, reducer

原理图

Redux工作流

Redux工作流

创建reducer

  • 可以使用单独的一个reducer,也可以将多个reducer合并为一个reducer,即:combineReducers()
  • action发出命令后将state放入reucer加工函数中,返回新的state,对state进行加工处理

创建action

  • 用户是接触不到state的,只能有view触发,所以,这个action可以理解为指令,需要发出多少动作就有多少指令
  • action是一个对象,必须有一个叫type的参数,定义action类型

创建的store,使用createStore方法

  • store 可以理解为有多个加工机器的总工厂
  • 提供subscribe,dispatch,getState这些方法。

举例:

赵政委的主要功能

  • 老赵有一个保险箱(store),所有人的状态,在那里都有记录(state)
  • 需要改变的时候,需要告诉专员(dispatch)要干什么(action)
  • 处理变化的人(reducer)拿到state和action,生成新的state

走马上任

  • 首先通过reducer新建store,随时通过store.getState获取状态
  • 需要状态变更,store.dispatch(action)来修改状态
  • Reducer函数接受state和action,返回新的state,可以用store.subscribe监听每次的修改

官方案例:

import { createStore } from 'redux';
/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数。
 * 描述了 action 如何把 state 转变成下一个 state。
 *
 * state 的形式取决于你,可以是基本类型、数组、对象、
 * 甚至是 Immutable.js 生成的数据结构。惟一的要点是
 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
 *
 * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
 * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
2 Redux如何和React一起使用

手动连接,老赵怎么管理独立团呢

  • 把store.dispatch 方法传递给组件,内部可以调用修改状态
  • Subscribe订阅render函数,每次修改都重新渲染
  • Redux相关内容,移到单独的文件index.redux.js单独管理

在src目录下分别redux/action/ redux/reducer/ redux/store/

在 action 目录下新建 index.js

/**
 * Action类型:用户事件操作
 * @type {{INCREMENT: string, DECREMENT: string}}
 */
export const type = {
    INCREMENT: 'INCREMENT',
    DECREMENT: 'DECREMENT'
}
export function add(){
    return {
        type: type.INCREMENT
    }
}
export function remove(){
    return {
        type: type.DECREMENT
    }
}

在 reducer 目录下新建 index.js

/*
 * Reducer: 数据处理
 */
 import { type } from "./../action";
 const counter = (state=0, action) => {
     switch (action.type) {
       case type.INCREMENT:
           return state + 1
       case type.DECREMENT:
           return state - 1
       default:
           return state;
     }
 }
 export default counter;

在 store 目录下新建 index.js

/*
 *  引入createStore 保存数据源
 */
import { createStore } from "redux";
// 引入所有的reducer
import reducer from "./../reducer";
export default ()=>createStore(reducer)

修改src根目录下的index.js

import configureStore from './redux/store/index';
const store = configureStore();
function render(){
    ReactDOM.render(<App store={store}/>, document.getElementById('root'));
}
render()
store.subscribe(render)

修改App.js 文件

import React from 'react';
import './App.css';
import {Button, Flex, WingBlank} from 'antd-mobile';
import {add, remove} from './redux/action'
class App extends React.Component{
    state = {
        count: 0
    }
    static defaultProps = {
        num: 1
    }
    add = () => {
        this.setState({
            count: this.state.count+1
        })
    }
    remove = () => {
        this.setState({
            count: this.state.count-1
        })
    }
    render(){
        const store = this.props.store
        const num = store.getState()
        return (
            <div>
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={this.add}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{this.state.count}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={this.remove}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(add())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(remove())}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
            </div>
        );
    }
}
export default App;
3 处理异步、调试工具、更优雅的和React结合
  • Redux处理异步,需要redux-thunk插件
  • Npm install redux-devtools-extension并且开启
  • 使用react-redux优雅的链接react和redux

处理异步

Redux 默认只处理同步,异步任务需要react-thunk中间件

  • 安装及使用redux-thunk插件
    • yarn add redux-thunk --save
    • 使用applyMiddleware开启thunk中间件
    • Action可以返回函数,使用dispatch提交action

示例:

修改 src/redux/store/index.js

/*
 *  引入createStore 保存数据源
 */
import {createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk';
// 引入所有的reducer
import counter from "./../reducer";
export default () => createStore(counter, applyMiddleware(thunk))

同时在 src/redux/action/index.js 代码中新增

export function addAsync() {
    return dispatch => {
        setTimeout(() => {
            dispatch(add())
        }, 2000)
    }
}

在 App.js 代码中新增

import {add, remove, addAsync} from './redux/action'
<Flex.Item><Button type="primary" onClick={() => store.dispatch(addAsync())}>
                            +
                        </Button></Flex.Item>
4 调试工具

Chrome 搜索 redux 安装

  • 新建store的时候判断window.devToolsExtension
  • 使用compose结合thunk和window.devToolsExtension
  • 调试窗口的redux选项卡,实时看到state
5 使用react-redux
  • npm install react-redux --save
  • 忘记subscribe,记住reducer, action 和 dispatch即可
  • React-redux提供Provider和connect俩个接口来链接

React-redux 具体使用

  • Provider 组件在应用最外层,传入store即可,只用一次
  • Connect 负责从外部获取组件需要的参数
  • Connect 可以用装饰器的方式来写

示例:

修改 src/index.js 添加项目根组件

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import configureStore from './redux/store/index';
import { Provider } from "react-redux"; // 添加<Provider />项目根组件
import App from './App';
import * as serviceWorker from './serviceWorker';
const store = configureStore();
/*function render(){
    ReactDOM.render(<App store={store}/>, document.getElementById('root'));
}
render()
store.subscribe(render)*/
ReactDOM.render(
    (<Provider store={store}>
        <App/>
    </Provider>),
    document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

修改 src/App.js 文件 引入 react-redux connect

import React from 'react';
import './App.css';
import {connect} from 'react-redux';
import {Button, Flex, WingBlank} from 'antd-mobile';
import {add, remove, addAsync} from './redux/action'
class App extends React.Component{
    state = {
        count: 0
    }
    static defaultProps = {
        num: 1
    }
    add = () => {
        this.setState({
            count: this.state.count+1
        })
    }
    remove = () => {
        this.setState({
            count: this.state.count-1
        })
    }
    render(){
        /*const store = this.props.store
        const num = store.getState()*/
        const num = this.props.num;
        const addAsync = this.props.addAsync;
        const remove = this.props.remove;
        return (
            <div>
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={this.add}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{this.state.count}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={this.remove}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
                {/*<WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(addAsync())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(add())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(remove())}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>*/}
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => addAsync()}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => remove()}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
            </div>
        );
    }
}
//将state.num 绑定到 props 的num
const mapStateToProps = state => {
    console.log(state);
    return {
        num: state
    };
};
const actionCreators = {add, remove, addAsync}
export default connect(mapStateToProps,actionCreators)(App);

其中的代码修改处

/*const store = this.props.store
        const num = store.getState()*/
        const num = this.props.num;
        const addAsync = this.props.addAsync;
        const remove = this.props.remove;
{/*<WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(addAsync())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(add())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(remove())}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>*/}
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => addAsync()}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => remove()}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>

最后将state.num 绑定到 props 的num

//将state.num 绑定到 props 的num
const mapStateToProps = state => {
    console.log(state);
    return {
        num: state
    };
};
const actionCreators = {add, remove, addAsync}
export default connect(mapStateToProps,actionCreators)(App);
6 使用装饰器优化connect代码
  • Npm run eject 弹出个性化配置
  • Npm install babel-plugin-transform-decorators-legacy插件
  • package.json 里 babel加上plugins配置

yarn add babel-plugin-transform-decorators-legacy --save-dev

babel-plugin-transform-decorators-legacy

进而可以使用注解的方式

示例:

安装plugins

{
 "plugins": [
      ["@babel/plugin-proposal-decorators", { "legacy": true }],
      [
        "import",
        {
          "libraryName": "antd-mobile",
          "style": "css"
        }
      ]
    ]
}

修改 src/App.js 代码

import React from 'react';
import './App.css';
import {connect} from 'react-redux';
import {Button, Flex, WingBlank} from 'antd-mobile';
import {add, remove, addAsync} from './redux/action'
//将state.num 绑定到 props 的num
/*const mapStateToProps = state => {
    console.log(state);
    return {
        num: state
    };
};
const actionCreators = {add, remove, addAsync}
@connect(
    mapStateToProps,actionCreators
)*/
@connect(
    state=>({num: state}),
    {add, remove, addAsync}
)
class App extends React.Component{
    state = {
        count: 0
    }
    static defaultProps = {
        num: 1
    }
    add = () => {
        this.setState({
            count: this.state.count+1
        })
    }
    remove = () => {
        this.setState({
            count: this.state.count-1
        })
    }
    render(){
        /*const store = this.props.store
        const num = store.getState()*/
        const num = this.props.num;
        const addAsync = this.props.addAsync;
        const remove = this.props.remove;
        return (
            <div>
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={this.add}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{this.state.count}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={this.remove}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
                {/*<WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(addAsync())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(add())}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => store.dispatch(remove())}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>*/}
                <WingBlank>
                    <Flex>
                        <Flex.Item><Button type="primary" onClick={() => addAsync()}>
                            +
                        </Button></Flex.Item>
                        <Flex.Item style={{textAlign: 'center'}}>{num}</Flex.Item>
                        <Flex.Item><Button type="primary" onClick={() => remove()}>
                            -
                        </Button></Flex.Item>
                    </Flex>
                </WingBlank>
            </div>
        );
    }
}
// export default connect(mapStateToProps,actionCreators)(App);
export default App;

taro + taro-ui + dva

我们引入dva

npm i dva-core dva-loading --save

新建dva.js

import { create } from 'dva-core';
import { createLogger } from 'redux-logger';
import createLoading from 'dva-loading';
 
let app;
let store;
let dispatch;
 
function createApp(opt) {
  // redux日志
  // opt.onAction = [createLogger()];
  app = create(opt);
  app.use(createLoading({}));
 
  if (!global.registered) opt.models.forEach(model => app.model(model));
  global.registered = true;
  app.start();
 
  store = app._store;
  app.getStore = () => store;
 
  dispatch = store.dispatch;
 
  app.dispatch = dispatch;
  return app;
}
 
export default {
  createApp,
  getDispatch() {
    return app.dispatch;
  }
}

并在入口文件导入

import dva from './utils/dva'
const dvaApp = dva.createApp({
  initialState: {},
  models: models,
});
const store = dvaApp.getStore();

封装下request网络请求

import Taro from '@tarojs/taro';
import { baseUrl, noConsole } from '../config';
 
export default (options = { method: 'GET', data: {} }) => {
  if (!noConsole) {
    console.log(`${new Date().toLocaleString()}【 M=${options.url} 】P=${JSON.stringify(options.data)}`);
  }
  return Taro.request({
    url: baseUrl + options.url,
    data: options.data,
    headers: {
      'Content-Type': 'application/json',
    },
    method: options.method.toUpperCase(),
  }).then((res) => {
    const { statusCode, data } = res;
    if (statusCode >= 200 && statusCode < 300) {
      if (!noConsole) {
        console.log(`${new Date().toLocaleString()}【 M=${options.url} 】【接口响应:】`,res.data);
      }
      if (data.status !== 'ok') {
        Taro.showToast({
          title: `${res.data.error.message}~` || res.data.error.code,
          icon: 'none',
          mask: true,
        });
      }
      return data;
    } else {
      throw new Error(`网络请求错误,状态码${statusCode}`);
    }
  })
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值