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 中时,其生命周期调用顺序如下:
static getDerivedStateFromProps()
componentwillMount 将被舍弃
更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps()
getSnapshotBeforeUpdate()
卸载
当组件从 DOM 中移除时会调用如下方法:
其他 APIs 组件还提供了一些额外的 API:
class 属性
复制 class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: "blue",
};
如果 props.color 被设置为 null,则它将保持为 null render() {
复制 return <CustomButton color={null} /> ; // props.color 将保持是 null
实例属性
了解了一下小程序的生命周期:
应用级别的生命周期 onLaunch onShow onHide
onLaunch 是当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
onShow 是当小程序启动,或从后台进入前台显示,会触发 onShow;
onHide 是当小程序从前台进入后台,会触发 onHide;
页面的生命周期 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>
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 中使用函数式组件有以下限制:
一个文件中只能定义一个普通函数式组件或一个 Class 组件
由于一个文件不能定义两个组件,但有时候我们需要组件内部的抽象组件
函数的命名必须以 render 开头,render 后的第一个字母需要大写
函数的参数不得传入 JSX 元素或 JSX 元素引用
报没有 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 达到同等的需求,其中就包括但不限于:
使用 this.$scope.triggerEvent 调用通过 props 传递的函数;
通过 this.$scope.selectComponent 和 wx.createSelectorQuery 实现 ref;
通过 getCurrentPages 等相关方法访问路由;
修改编译后文件 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 的属性可以省略书写值