Skip to content

一. 自定义组件


1. 组件及生命周期

  • Taro 中,除了应用和页面组件有生命周期之外, Taro 的组件也是生命周期,如下图所示:

    class 组件Hooks 组件
    constructoruseState
    getDerivedStateFromPropsuseState 里面 update 函数
    shouldComponentUpdateuseMemo
    render函数本身
    componentDidMountuseEffect
    componentDidUpdateuseEffect
    componentWillUnmountuseEffect 里面返回的函数
    componentDidCatch
    getDerivedStateFromError
  • 下面我们来编写一个自定义 Button 组件(详细查看代码文件)

    • 创建组件
    • 定义属性
    • 样式编写
    • 定义插槽
    • 定义生命周期
    • 推荐安装classnamesprop-types这两个库来进行增强
  • 组件可编写页面生命周期吗?

    • class 组件默认不行,需要单独处理
    • 但是函数组件是支持的
  • 页面可以编写组件生命周期吗?可以

二. 跨端兼容实现


1. 跨端兼容方案

  • Taro 的设计初衷就是为了统一跨平台的开发方式,并且已经尽力通过运行时框架、组件、API 去抹平多端差异,但是由于不同的平台之间还是存在一些无法消除的差异,所以为了更好的实现跨平台开发,Taro 中提供了如下的解决方案
  • 方案一:内置环境变量
    • Taro 在编译时提供了一些内置的环境变量来帮助用户做一些特殊处理
    • 通过这个变量来区分不同环境,从而使用不同的逻辑。在编译阶段,会移除不属于当前端的代码,只保留当前端的代码
    • 内置环境变量虽然可以解决大部分跨端的问题,但是会让代码中存在很多逻辑判断的代码,影了响代码的可维护性,而且也让代码变得丑陋
    • 为了解决这种问题,Taro 提供了另外一种跨端开发的方式作为补充
  • 方案二:统一接口的多端文件
    • 开发者可以通过将文件修改成 原文件名 + 端类型 的命名形式(端类型对应着 process.env.TARO_ENV 的取值),不同端的文件代码对外保持统一接口,而引用的时候仍然是 import 原文件名的文件
    • Taro 在编译时,会跟根据当前编译平台类型,精准加载对应端类型的文件,从而达到不同的端加载其对应端的文件

2. 内置环境变量

  • 内置环境变量( process.env.TARO_ENV ),该环境变量可直接使用

    • process.env.TARO_ENV,用于判断当前的编译平台类型,有效值为:weapp / swan / alipay / tt / qq / jd / h5 / rn

    • 通过这个变量来区分不同环境,从而使用不同的逻辑

    • 在编译阶段,会移除不属于当前平台的代码,只保留当前平台的代码,例如:

      jsx
      /** 源码(React JSX) */
      <View>
        {process.env.TARO_ENV === 'weapp' && <ScrollViewWeapp />}
        {process.env.TARO_ENV === 'h5' && <ScrollViewH5 />}
      </View>
      
      /** 编译后(微信小程序)*/
      <View>
        {true && <ScrollViewWeapp />}
      </View>
      /** 编译后(H5)*/
      <View>
        {true && <ScrollViewH5 />}
      </View>

注意:

  • 不要解构 process.env 来获取环境变量,请直接以完整书写的方式(process.env.TARO_ENV)来进行使用

3. 统一接口的多端文件

  • 统一接口的多端文件这一跨平台兼容写法有如下三个使用要点:

    1. 不同端的对应文件一定要统一接口和调用方式

    2. 引用文件的时候,只需写默认文件名,不用带文件后缀

      jsx
      import Test from '../../components/test'
      
      <Test argA={1} argA={2} />
    3. 最好有一个平台无关的默认文件,这样在使用 TS 的时候也不会出现报错

  • 常见有以下使用场景:

    1. 多端组件(属性,方法,事件等需统一)

      • 针对不同的端写不同的组件代码

        js
        ├── test.js                Test 组件默认的形式编译到微信小程序百度小程序和 H5 之外的端使用的版本
        ├── test.weapp.js          Test 组件的微信小程序版本
        ├── test.swan.js           Test 组件的百度小程序版本
        └── test.h5.js             Test 组件的 H5 版本
    2. 多端脚本逻辑(属性、方法等需统一)

      • 针对不同的端写不同的脚本逻辑代码

三. Redux状态管理


1. 认识 Redux Toolkit(RTK)

  • Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法

    • 以前我们在使用 redux 的时候,通常会将 redux 代码拆分在多个文件中,比如:constantsactionreducer
    • 这种代码组织方式过于繁琐和麻烦,导致代码量过多,也不利于后期管理
    • Redux Toolkit 就是为了解决这种编码方式而诞生
    • 并且以前的 createStore 方式已标为过时,而 Redux Toolkit 已成为官方推荐
  • 安装 Redux Toolkit

    bash
    npm i @reduxjs/toolkit react-redux
  • Redux Toolkit 的核心 API 主要是如下几个:

    • configureStore:包装 createStore 以提供简化的配置选项和良好的默认值
      • 可自动组合 slice reducer
      • 可添加其它 Redux 中间件,redux-thunk 默认包含
      • 默认启用 Redux DevTools Extension
    • createSlice:接受切片名称、初始状态值和 reducer 函数的对象,并自动生成切片 reducer,并带有相应的 actions
    • createAsyncThunk:接受一个动作类型字符串和一个返回承诺的函数,并生成一个 pending / fulfilled / rejected 基于该承诺分派动作类型的 thunk。简单理解就是专门用来创建异步 Action

2. 创建 counter 模块的 reducer

  • 我们先创建 counter 模块的 reducer: 通过 createSlice 创建一个 slice

  • createSlice 主要包含如下几个参数:

    • name:用来标记 slice 的名词,redux-devtool 中会显示对应的名词
    • initialState:第一次初始化时的值
    • reducers:相当于之前的 reducer 函数
      • 对象类型,并且可以添加很多的函数
      • 函数类似于 redux 原来 reducer 中的一个 case 语句
      • 函数的参数:
        • 参数一:state
        • 参数二:action
    • createSlice 返回值是一个对象
      • 对象包含所有的 actionsreducer
    js
    import { createSlice } from "@reduxjs/toolkit"
    
    const homeSlice = createSlice({
      name: "home", // 唯一id,这个名字会作为action type的前缀
      initialState: {
        count: 800,
      },
      reducers: {
        increment(state, action) {},
        decrement(state, action) {},
      },
    })
    
    export const { increment, decrement } = homeSlice.actions
    export default homeSlice.reducer

3. store 的创建

  • configureStore 用于创建 store 对象,常见参数如下:

    • reducer,将 slice 中的 reducer 可以组成一个对象传入此处
    • middleware:可以使用参数,传入其他的中间件(自行了解)
    • devTools:是否配置 devTools 工具,默认为 true
    js
    // 创建store对象, 合并子reducer (reducer的切片)
    import { configureStore } from "@reduxjs/toolkit"
    import homeReducer from "./modules/home"
    
    const store = configureStore({
      reducer: {
        home: homeReducer,
      },
    })
    
    export default store

4. store 接入应用

  • app.js 中将 store 接入应用:

    • Provider,内容提供者,给所有的子或孙子组件提供 store 对象
    • store: 使用 configureStore 创建的 store 对象
    jsx
    import { Component } from "react"
    import { Provider } from "react-redux"
    
    import "./app.scss"
    import store from "./store"
    
    class App extends Component {
      componentDidMount() {}
      componentDidShow() {}
      componentDidHide() {}
      render() {
        // this.props.children 是将要会渲染的页面
        return <Provider store={store}>{this.props.children}</Provider>
      }
    }
    
    export default App

5. 开始使用 store

  • 在函数式组件中可以使用 react-redux 提供的 Hooks API 连接、操作 store

    • useSelector 允许你使用 selector 函数从 store 中获取数据(root state
    • useDispatch 返回 redux storedispatch 引用。你可以使用它来 dispatch actions
    • useStore 返回一个 store 引用,和 Provider 组件引用完全一致
    jsx
    import { Component } from 'react'
    import { useSelector } from 'react-redux'
    
    export const CounterComponent = () => {
      const counter = useSelector(state => state.count)
      return <View>{counter}</View>
    }
    jsx
    import { Component } from 'react'
    import { useDispatch } from 'react-redux'
    
    export const CounterComponent = ({ value }) => {
      const dispatch = useDispatch()
      return (
      	<View>
        	<Text>{value}</Text>
          <Button onClick={() => dispatch({ type: 'increment-counter' })}>
            Increment counter
          </Button>
        </View>
      )
    }
    jsx
    import { Component } from 'react'
    import { useStore } from 'react-redux'
    
    export const CounterComponent = () => {
      const store = useStore()
      return <div>{store.getState()}</div>
    }

6. Redux Toolkit 异步 Action 操作

  • 在之前的开发中,我们通过 redux-thunk 中间件让 dispatch 中可以进行异步操作

  • Redux Toolkit 默认已经给我们继承了 Thunk 相关的功能:createAsyncThunk

    jsx
    export const fetchHomeMultiData = createAsyncThunk(
      'home.multidata', // 作为action type的前缀
      async (payload, extraInfo) => {
        console.log('payload: ', payload)
        console.log('extraInfo: ', extraInfo)
        const res = await getHomeMultidata()
        return res.data
      }
    )
  • createAsyncThunk 创建出来的 actiondispatch 时,会存在三种状态:

    • pendingaction 被发出,但是还没有最终的结果
    • fulfilled:获取到最终的结果(有返回值的结果)
    • rejected:执行过程中有错误或者抛出了异常
  • 我们可以在 createSliceentraReducer 中监听这些结果:

    jsx
    extraReducers: (builder) => {
      // 监听异步请求成功结果
      builder.addCase(fetchHomeMultiData.fulfilled, (state, action) => {
        const { payload } = action
        console.log('payload: ', payload) // 上面返回的结果 => res.data
        state.homeData = payload
      })	
    }

四. 卷皮项目实战


1. 卷皮项目

  • 小程序 appid:wxbc30134b589795b09(填自己的)
  • 项目安装的依赖:
    • @reduxjs/toolkit
    • react-redux
    • classnames
    • proptypes
image-20230201111227528

2. 项目打包和部署

  • 多端同步调试

    • 可以在 dist 目录下创建一个与编译的目标平台名同名的目录,并将结果放在这个目录下

    • 例如:编译到微信小程序,最终结果是在 dist/weapp 目录下; H5 打包结果放在 dist/h5 目录下

    • 好处是,各个平台使用独立的目录互不影响,从而达到多端同步调试的目的,在 config/index.js 配置如下:

      js
      // config/index.js
      // 配置输出目录
      outputRoot: `dist/${process.env.TARO_ENV}`
  • 对于微信小程序,还需要将 project.config.json 文件的 miniprogramRoot 的值改为 dist/weapp/,这样就可以正常在开发者工具中看到小程序了

  • 浏览器端

    • 打包:npm run build:h5
  • 微信小程序

    • 打包:npm run build:weapp
    • 微信开发者工具打开 weapp 目录进行预览或发包

Released under the MIT License.