Taro 浅析用法与原理

json 配置

  "postcss": {
    "plugins": {
      "autoprefixer": {},
      "postcss-px2rem": {
        "remUnit": 75
      },
      "postcss-assets": {
        "loadPaths": "[path.resolve(__dirname, 'src/assets/img')]",
        "relative": true
      }
    }
  },
class Demo extends Component {
  constructor(props) {
    super(props);
  }
  componentWillMount() {
    window.temp = "demo";
  }
}

生命周期

页面切换的执行顺序:

  • 离开的顺序: index 的跳转方法 后面跟着 componentdDidHide

  • 进入的顺序: componentWillMount componentDidShow componentDidMount

15 版本的生命周期与方法

  • constructor,顾名思义,组件的构造函数。一般会在这里进行 state 的初始化,事件的绑定等等

  • componentWillMount,是当组件在进行挂载操作前,执行的函数,一般紧跟着 constructor 函数后执行

  • componentDidMount,是当组件挂载在 dom 节点后执行。一般会在这里执行一些异步数据的拉取等动作

  • shouldComponentUpdate,返回 false 时,组件将不会进行更新,可用于渲染优化

  • componentWillReceiveProps,当组件收到新的 props 时会执行的函数,传入的参数就是 nextProps ,你可以在这里根据新的 props 来执行一些相关的操作,例如某些功能初始化等

  • componentWillUpdate,当组件在进行更新之前,会执行的函数

  • componentDidUpdate,当组件完成更新时,会执行的函数,传入两个参数是 prevProps 、prevState

  • componentWillUnmount,当组件准备销毁时执行。在这里一般可以执行一些回收的工作,例如 clearInterval(this.timer) 这种对定时器的回收操作

16 版本的生命周期与方法

  • constructor,顾名思义,组件的构造函数。一般会在这里进行 state 的初始化,事件的绑定等等

  • componentDidMount,是当组件挂载在 dom 节点后执行。一般会在这里执行一些异步数据的拉取等动作

  • shouldComponentUpdate,返回 false 时,组件将不会进行更新,可用于渲染优化

  • componentDidUpdate,当组件完成更新时,会执行的函数,传入两个参数是 prevProps 、prevState

  • componentWillUnmount,当组件准备销毁时执行。在这里一般可以执行一些回收的工作,例如 clearInterval(this.timer) 这种对定时器的回收操作

taro 版本对应的是 16 之前

去对比了一下 react 16 的生命周期

挂载 当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor()

  • static getDerivedStateFromProps()

  • render()

  • componentDidMount(-

componentwillMount 将被舍弃

更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps()

  • shouldComponentUpdate()

  • render()

  • getSnapshotBeforeUpdate()

  • componentDidUpdate()

卸载

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount()

其他 APIs 组件还提供了一些额外的 API:

  • setState()

  • forceUpdate()

class 属性

  • defaultProps

  • displayName

class CustomButton extends React.Component {
  // ...
}

CustomButton.defaultProps = {
  color: "blue",
};

如果 props.color 被设置为 null,则它将保持为 null render() {

return <CustomButton color={null} /> ; // props.color 将保持是 null

实例属性

  • props

  • state

了解了一下小程序的生命周期:

  • 应用级别的生命周期 onLaunch onShow onHide

  • onLaunch 是当小程序初始化完成时,会触发 onLaunch(全局只触发一次)

  • onShow 是当小程序启动,或从后台进入前台显示,会触发 onShow;

  • onHide 是当小程序从前台进入后台,会触发 onHide;

页面的生命周期 onLoad、onReady、onShow、onHide、onUnload

  • onLoad 是监听页面加载的函数

  • onReady 是监听页面初次渲染完成的函数

  • onShow 是监听页面显示的函数

  • onHide 是监听页面隐藏的函数

  • onUnload 是监听页面卸载的函数

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

if 语句和 for 循环在 JavaScript 中不是表达式,因此它们不能直接在 JSX 中使用,所以你可以将它们放在周围的代码中。

import Taro, {
    Component
} from '@tarojs/taro'

class App extends Components {
    render() {

        let description

        if (this.props.number % 2 == 0) {
            description = < Text > even < /Text>
        } else {
            description = < Text > odd < /Text>
        }

        return <View > {
            this.props.number
        }
    }
}

布尔值、Null 和 Undefined 被忽略

<View>
  {showHeader && <Header />}
  <Content />
</View>
  1. 不要直接更新状态

  2. 状态更新一定是异步的

Taro 可以将多个 setState() 调用合并成一个调用来提高性能。 因为 this.state 和 props 一定是异步更新的,所以你不能在 setState 马上拿到 state 的值,例如:

// 假设我们之前设置了 this.state.counter = 0
updateCounter () {
  this.setState({

    counter: 1

  }, () => {

    // 在这个函数内你可以拿到 setState 之后的值

  })
}

合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换 this.state.comments 的值。

当你通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面。

class Popper extends Component {
  constructor () {

    super(...arguments)
    this.state = { name:'Hello world!' }

  }

  // 你可以通过 bind 传入多个参数
  preventPop (name, test, e) {    //事件对象 e 要放在最后

    e.stopPropagation()

  }

  render () {

    return <Button onClick={this.preventPop.bind(this, this.state.name, 'test')}></Button>

  }

const App = () => {
  const [c1, setC1] = useState(0);
  const [c2, setC2] = useState(0);
  const [c3, setC3] = useState(0);

  const increment = c => c + 1

  // 只有 useCallback 对应的 c1 或 c2 的值改变时,才会返回新的函数
  const increment1 = useCallback(() => setC1(increment), [c1]);
  const increment2 = useCallback(() => setC2(increment), [c2]);

  return (<View>

    <Text> Counter 1 is {c1} </Text>
    <Text> Counter 2 is {c2} </Text>
    <Text> Counter 3 is {c3} </Text>
    <View>
      <Button onClick={increment1}>Increment Counter 1</Button>
      <Button onClick={increment2}>Increment Counter 2</Button>
      <Button onClick={() => setC3(increment)}>Increment Counter 3</Button>
    </View>

  </View>)
}

      <Dialog
        renderHeader={
            <View className='welcome-message'>Welcome!</View>
          }
        renderFooter={
            <Button className='close'>Close</Button>
          }
        ref={this.refDialog}
      >
          <View className='dialog-message'>
            Thank you for using Taro.
          </View>
        </Dialog>

      <MList />
// JSX 语法
const num = 1;
const hello = <div className="test">{num}</div>;

// 编译后的 JS
const num = 1;
const hello = React.createElement("div", { className: "test" }, num);

然而,在 JSX 里使用 JS 是有限制的,只能使用一些表达式,不能定义变量,使用 if/else 等,你可以用提前定义变量;用三元表达式来达到同样的效果。

组件中如果收到了新的 props,就会重新执行一次 render 函数,也就是重新渲染一遍。

constructor(props) {

    super(props);
    this.state = {name: 'aotu,taro!'};

  }

下面这种写法可以尝试下:

  render () {

    const { showHeader, showMain } = this.state
    const header = showHeader && <Header />
    const main = showMain && <Main />
    return (
      <View>
        {header}
        {main}
      </View>
    )
  }

api

微信提供的回调是 bindgetuserinfo,但是 Taro 将 bind 事件都封装成了 on 事件,这个需要注意一下

componentWillReact

    //天数计算
    int days = (num)/(24*3600);

    //小时计算
    int hours = (num)%(24*3600)/3600;

    //分钟计算
    int minutes = (num)%3600/60;

    //秒计算
    int second = (num)%60;

const SHAREINFO = {
  'title': '分享标题',
  'path': '路径',
  'imageUrl': '图片'
}

Component.prototype.onShareAppMessage = function () {
  return SHAREINFO
}
Taro.authorize({
  scope: "scope.userInfo",
  success(data) {
    // 用同意小程序使用录音功能,后续调用 wx.startRecord 接口不会弹窗询问
    userStore.updateInfo(data.userInfo.avatarUrl, data.userInfo.nickName);
  },
});
Taro.getStorage({
  key: "key",
  success: function (res) {
    console.log(res.data);
  },
});
try {
  var value = Taro.getStorageSync("key");
  if (value) {
    // Do something with return value
  }
} catch (e) {
  // Do something when catch error
}

遇到的坑

最佳实践

不能使用 wx:for , 只能使用 map 方法

无效使用方案

numbers.map((number) => {
  let element = null;
  const isOdd = number % 2;
  if (isOdd) {
    element = <Custom />;
  }
  return element;
});

numbers.map((number) => {
  let isOdd = false;
  if (number % 2) {
    isOdd = true;
  }
  return isOdd && <Custom />;
});

有效解决方案

numbers.map((number) => {
  const isOdd = number % 2;
  return isOdd ? <Custom /> : null;
});

numbers.map((number) => {
  const isOdd = number % 2;
  return isOdd && <Custom />;
});

解决方案:

numbers.filter(isOdd).map((number) => <View />);

for (let index = 0; index < array.length; index++) {
  // do you thing with array
}

const element = array.map((item) => {
  return <View />;
});

不能在 jsx 参数中使用匿名函数

<View onClick={() => this.handleClick()} />

<View onClick={(e) => this.handleClick(e)} />

<View onClick={() => ({})} />

<View onClick={function () {}} />

<View onClick={function (e) {this.handleClick(e)}} />

可以使用 bind 或者类参数

跟 vue 不同的点。不允许在 jsx 参数中传入 jsx 元素 不能使用内置组件化的 slot 功能

无效方案

<Custom child={<View />} />

<Custom child={() => <View />} />

<Custom child={function () { <View /> }} />

<Custom child={ary.map(a => <View />)} />

解决方案

通过 props 传值在 JSX 模板中预先判定显示内容,或通过 props.children 来嵌套子组件。 小程序端不要在组件中打印传入的函数 this.props.onXxx && this.props.onXxx() 这种判断函数是否传入来进行调用的写法是完全支持的。

render () { // 增加一个兼容判断 return this.state.abc && }

在微信小程序中,从调用 Taro.navigateTo、Taro.redirectTo 或 Taro.switchTab 后,到页面触发 componentWillMount 会有一定延时。因此一些网络请求可以提前到发起跳转前一刻去请求。

样式层面

分享按钮 包裹图片时出现点击无效 要设置 z-index

结合云函数

这里我用到了上传文件的功能, 结合存储

Taro.cloud.downloadFile({
  fileID: this.fileID, // 文件 ID
  success: (res) => {
    // 返回临时文件路径
    console.log("tempFilePath", res.tempFilePath);
    that.setState({ videoUrl: res.tempFilePath });
  },
  fail: console.error,
});

文件主要分为两大类:代码包文件和本地文件(上限 50M)。

本地文件是通过调用接口本地生成,或通过网络下载后存储到本地的文件,包括本地临时文件、本地缓存文件和本地用户文件。

Taro.chooseMessageFile({
  count: 10,
  type: "file",
  success(res) {
    // tempFilePath可以作为img标签的src属性显示图片
    const tempFilePaths = res.tempFiles[0].path;
    const name = Math.random() * 1000000;
    const cloudPath = name + tempFilePaths.match(/\.[^.]+?$/)[0];
    Taro.$upload(cloudPath, tempFilePaths);
    console.log("tempFilePaths: ", cloudPath, tempFilePaths);
  },
});
Taro.loadFontFace({
  family: "Bitstream Vera Serif Bold",
  source: 'url("https://sungd.github.io/Pacifico.ttf")',
  success: console.log,
});
Taro.chooseImage({
  sizeType: ["original", "compressed"], // 可以指定是原图还是压缩图,默认二者都有
  sourceType: ["album", "camera"], // 可以指定来源是相册还是相机,默认二者都有
  success: function (res) {
    Taro.showLoading({
      title: "上传中",
    });
    // 返回选定照片的本地文件路径列表,tempFilePath可以作为img标签的src属性显示图片
    let filePath = res.tempFilePaths[0];
    const name = Math.random() * 1000000;
    const cloudPath = name + filePath.match(/\.[^.]+?$/)[0];
    Taro.$upload(cloudPath, filePath);
  },
  fail: (e) => {
    console.error("[上传图片] 失败:", e);
  },
  complete: () => {
    Taro.hideLoading();
  },
});
const refDay = (node) => this.MDay = node // `this.MDay` 会变成 `MDay` 组件实例的引用
const refDialog = (node) => this.MDialog = node // `this.MDialog` 会变成 `MDialog` 组件实例的引用
二) 常量定义
1. 【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中;
2. 【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。 如:缓存相关的常量放在类:CacheConsts下; 系统配置相关的常量放在类:ConfigConsts下; 说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护;
【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长; 正例: MAX_STOCK_COUNT 反例: MAX_COUNT

    const vStyle = classNames({
      playing: true,
      'vStyle-a': id === 'A',
      'vStyle-b': id === 'B',
      'vStyle-c': id === 'C'
    });
    const pStyle = classNames(
      'circle',
      { 'whiteCircle': playState === 'PLAY_START' },
      { 'blueCircle': playState === 'PLAY_LOAD' },
      { 'whiteCircle': playState === 'PLAY_STOP' }
    )

<View className={vStyle}>
            <View className={`${pStyle}`} onClick={this.clickPlay}>
              {Triangle
              ?<Image className='Triangle' src={play}></Image>
              :
              <Image className='Triangle' src={stop}></Image>
              }
            </View>


taro 组件使用 keys 小程序原生组件使用 taroKeys

const numbers = [...Array(100).keys()]; // [0, 1, 2, ..., 98, 99]
const listItems = numbers.map((number) => {
  return (
    // native component
    <g-list taroKey={String(number)} className="g-list">
      我是第 {number + 1} 个数字
    </g-list>
  );
});

Taro 中,JSX 会编译成微信小程序模板字符串,因此你不能把 map 函数生成的模板当做一个数组来处理。当你需要这么做时,应该先处理需要循环的数组,再用处理好的数组来调用 map 函数。例如上例应该写成:

const list = this.state.list
  .filter((l) => l.selected)
  .map((l) => {
    return <li>{l.text}</li>;
  });

在 Taro 中使用函数式组件有以下限制:

  1. 函数的命名需要遵循帕斯卡式命名法;

  2. 一个文件中只能定义一个普通函数式组件或一个 Class 组件

由于一个文件不能定义两个组件,但有时候我们需要组件内部的抽象组件

  1. 函数的命名必须以 render 开头,render 后的第一个字母需要大写

  2. 函数的参数不得传入 JSX 元素或 JSX 元素引用

  3. 函数不能递归地调用自身

报没有 jsx 错误

  renderFooter(){
    const todos = [{id:1,text:1, completed:true}, {id:2, text:2, completed: false}, {id:3,text:3, completed: true}]
    const contain = todos.map(todo=>{
      <View key={todo.id} id={todo.id} >
        <Text>{todo.text}</Text>
        <Text>{todo.completed}</Text>
      </View>
    })
    return (contain)
  }

slot

const MyContext = Taro.createContext(defaultValue);

Children 与组合 相当于 slot 请不要对 this.props.children 进行任何操作。 this.props.children && this.props.children、this.props.children[0] 在 Taro 中都是非法的。 this.props.children 无法用 defaultProps 设置默认内容。 不能把 this.props.children 分解为变量再使用

通过字符串创建 ref 只需要把一个字符串的名称赋给 ref prop

// 如果 ref 的是小程序原生组件,那只有在 didMount 生命周期之后才能通过
// this.refs.input 访问到小程序原生组件
if (process.env.TARO_ENV === "weapp") {
  // 这里 this.refs.input 访问的时候通过 `wx.createSeletorQuery` 取到的小程序原生组件
} else if (process.env.TARO_ENV === "h5") {
  // 这里 this.refs.input 访问到的是 `@tarojs/components` 的 `Input` 组件实例
}

通过传递一个函数创建 ref, 在函数中被引用的组件会作为函数的第一个参数传递。

通过函数创建的 ref 是不是不能在函数式组件中使用 果然如此

  this.cat = Taro.createRef()

  roar () {
    // 会打印 `miao, miao, miao~`
    this.cat.current.miao()
  }

你基本都能使用小程序本身提供的 API 达到同等的需求,其中就包括但不限于:

  1. 使用 this.$scope.triggerEvent 调用通过 props 传递的函数;

  2. 通过 this.$scope.selectComponent 和 wx.createSelectorQuery 实现 ref;

  3. 通过 getCurrentPages 等相关方法访问路由;

  4. 修改编译后文件 createComponent 函数创建的对象

this.setState({
  value: this.state.value + 1,
}); // ✗ 错误

this.setState((prevState) => ({ value: prevState.value + 1 })); // ✓ 正确

尽量避免在 componentDidMount 中调用 this.setState 因为在 componentDidMount 中调用 this.setState 会导致触发更新

尽量避免,可以在 componentWillMount 中处理

不要在调用 this.setState 时使用 this.state 由于 this.setState 异步的缘故,这样的做法会导致一些错误,可以通过给 this.setState 传入函数来避免

不要在 componentWillUpdate/componentDidUpdate/render 中调用 this.setState

组件最好定义 defaultProps
  static defaultProps = {
    isEnable: true
  }

r//ender 方法必须有返回值

值为 true 的属性可以省略书写值

最后更新于

这有帮助吗?