taro简单介绍(美团外卖首页)
taro 简介
- 2018 年:一套遵循 react 语法规范的多端统一开发框架。
- 背景是 2016 年腾讯推出微信小程序,整合了开发体系,给开发者提供了很多的 API,开发者也可以在小程序上进行扩展。目的是消灭安卓和 ios 的分歧,能以微信作为入口去打开应用,这样写一套代码就可以在安卓、ios 上进行使用。以微信作为流量入口,由他来进行分发。taro 野心更大一些,一套代码来适用多端,一套代码处处运行比微信更为广义
基本介绍
- 在 render 中跟传统的 h5 开发不同的是并不是使用传统的 h5 标签,因为如果写 h5 标签如 div,放到小程序里面可能就不好使了
- 如果只做 h5 段,完全可以把 taro 当作 react 来用(实际上不会这么使用)。但是如果要适配多端,就得按照 taro 的规范来使用
- state
- taro 中的状态管理也是通过 state。react 中 state 的更新有可能是同步,有可能是异步;但是在 taro 中 state 的更新一定是异步的
- 设置状态必须用 setState
- 想拿到最新状态需要在回调函数当中
- 注意引入组件的名称必须和定义组件的名称保持一致(小程序的要求)
- props
- 通过 props 传入的属性如果在 dom 中显示:this.props.name, 可能会报错:"cannot read property 'value' of null",需要加上默认值 类组件加默认值的方式: Child.defaultProps={name: "aa"}
- props 是只读的
- 当传递 props 的值是函数时,必须用
on+函数名
的规范来命名
- 在 taro 中写方法不能直接写箭头函数:<Button onClick={()=>{}}>aaa会报错 需要通过 onClick=this.handleClick 来实现
- 生命周期
- h5 和小程序有差别
- 凡是 react 中需要的生命周期在 taro 中都可以使用 (完全支持 h5)
-
新增支持小程序的几个生命周期(对小程序端的生命周期进行了适配)
- 小程序端是区分页面和组件的,h5 在开发的时候认为一切皆组件
taro 实战-美团外卖首页
- less 中 尺寸设置成 height: 28PX, 像素用 PX 大写,防止转换成 rem
- 模糊背景的样式: filter: blur(6PX);
- 头部布局:
- Top 组件 className='top'=>头部操作栏 左边是返回按钮 右边是一排图标
放置背景图
- 商户信息 左边图片 右边描述
.top {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
.head {
.back {
width: 100%;
height: 150px;
filter: blur(6px);
}
.store {
display: flex;
flex-direction: row;
margin-left: 20px;
margin-top: -90px;
.store_img {
width: 80px;
height: 80px;
border-radius: 6px;
}
.store_text {
z-index: 2;
margin-left: 10px;
display: flex;
flex-direction: column;
font-size: 18px;
}
}
}
- 美团首页-中部区域
- 用到 taro-ui 实现 tab 切换
- 注意: 数组循环渲染 dom 只能放在 render 里面来做,不能抽离成方法放在外面去做。因为 taro 中不单单是有 h5,还有小程序。小程序是模板化的,现在的编译支持不了。
- 整个包裹在 Food 组件中
- 上面是标签页 保存 tabs 数组 当前所在的标签页 current
- 菜单分类组件 Cata
- 菜品列表组件 FoodList(与菜品分类组件存在交互 展示菜品列表)
- 加减菜品组件 AddCut 是放在 FoodList 组件的列表循环里面 每一条数据上都有一个 AddCut 组件
- Cata 和 FoodList 组件间数据传递采用父组件向下传递属性的方式来实现
- Cata 点击的分类发生变化 通知父组件
- 父组件加载数据 切换分类的方法 changeCata 传递给 Cata 组件
- 父组件将数据传递给 FoodList 组件中进行渲染
- 加减菜品组件
- 缓存数据,重新进入的时候仍然能够渲染已经加到多少 每一条菜品右边都会有菜品数量信息
- 缓存数据 因为是多端,有 h5,有小程序,所以这时候缓存不能用 h5 的方法也不能用小程序的方法,需要用 Taro 中提供的方法
Taro.getStorageSync
- 右边的列表每一项中都有一个 AddCut 组件,当左侧点击分类的时候,要重新去渲染 AddCut 中的数字内容, 是有很多而不是一个,所以不要通过上层父组件传值的方式来解决,会导致所有的列表组件都必须强制的去更新,父组件去调自组建的方法,这样做并不好
- 好的方式是: 定义一个大的事件池,在 AddCut 子组件中定义事件,左侧的分类当点击的时候触发事件,就会执行 AddCut 中定义的方法,实现菜品数量的更新
state = {
current: 0,
tabList: [{title: '点菜'},{title:'评价'},{title: '商家'}],
foodList: [],
currentList: [],
}
changeTab(value) {
this.setState({current: value})
}
changeCata(selectCata) {
if(this.state.foodList.some(item => item.pid === selectCata.id)) {
this.setState({
currentList: this.state.foodList.filter(item => item.pid === selectCata.id)
})
}else{
const data = this.getData(selectCata);
this.setState({foodList: this.state.foodList.concat(data)}, () => {
this.setState({
currentList: this.state.foodList.filter(item => item.pid === selectCata.id)
})
})
}
}
render() {
let {current, tabList, currentList} = this.state;
return (
<View>
<AtTabs
current={current}
onClick={this.changeTab.bind(this)}
tabList={tabList}
>
<AtTabsPane>
<View>
<Cata onChangeCata={this.changeCata.bind(this)}/> // 左侧是分类
<FoodList currentList={currentList}/> // 右侧是列表
</View>
</AtTabsPane>
<AtTabsPane>
评价
</AtTabsPane>
<AtTabsPane>
商家
</AtTabsPane>
</AtTabsPane>
</View>
)
}
import Taro from '@tarojs/taro'
const foodKey = 'taro_meituan'
function getFoodCount(food) {
let store = Taro.getStorageSync(foodKey)
if (store) {
if (store[food.id]) {
return store[food.id].num
} else {
return 0
}
} else {
return 0
}
}
function setFoodCount(food, num, type, callback) {
if (food) {
let store = Taro.getStorageSync(foodKey)
if (!store) {
store = {}
}
if (type == 'cut') {
if (num === 1) {
if (store[food.id]) {
delete store[food.id]
}
} else {
if (store[food.id]) {
store[food.id].num = num - 1
}
}
Taro.setStorageSync(foodKey, store)
callback && callback()
}
if (type == 'add') {
if (store[food.id]) {
store[food.id].num = num + 1
} else {
store[food.id] = { ...food, num: 1 }
}
Taro.setStorageSync(foodKey, store)
callback && callback()
}
}
}
class Event {
constructor() {
this.events = {}
}
on(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName].push(callback)
} else {
this.events[eventName] = [callback]
}
}
emit(eventName, params) {
if (this.events[eventName]) {
this.events[eventName].forEach((callback) => {
callback(params)
})
}
}
}
let myEvent = new Event()
function getEvent() {
return myEvent
}
import {getEvent} from '';
let myEvent = getEvent();
class AddCut extends Component {
constructor() {
super(...arguments)
this.state = {
num: 0,
}
}
componentDidMount() {
this.setState({ num: getFoodCount(this.props.food) })
myEvent.on('changeCata', () => {
this.setState({ num: getFoodCount(this.props.food) })
})
}
CutFood() {
if (this.props.food) {
if (this.state.num > 0) {
setFoodCount(this.props.food, this.state.num, 'cut', () => {
this.setState({ num: getFoodCount(this.props.food) })
myEvent.emit('addcut')
})
}
}
}
AddFood() {
if (this.props.food) {
setFoodCount(this.props.food, this.state.num, 'add', () => {
this.setState({ num: getFoodCount(this.props.food) })
myEvent.emit('addcut')
})
}
}
}
const myEvent = getEvent();
clickHandle(item) {
const { selectCata } = this.state;
if(selectCata && selectCata.id !== item.id) {
this.setState({ selectCata: item}, () => {
this.props.onChangeCata&&this.props.onChangeCata(this.state.selectCata)
})
myEvent.emit('changeCata');
}else if(!selectCata) {
this.setState({ selectCata: item}, () => {
this.props.onChangeCata&&this.props.onChangeCata(this.state.selectCata)
})
myEvent.emit('changeCata');
}
}
export function getAllFoodInfo() {
let allPrice = 0
let allNum = 0
let store = Taro.getStorageSync(foodKey)
if (store) {
Object.keys(store).map((key) => {
if (store[key]) {
allPrice += store[key].price * store[key].num
allNum += store[key].num
}
})
}
return { allPrice, allNum }
}
let myEvent = getEvent();
class AddCut extends Component {
constructor() {
super(...arguments)
this.state = {
allPrice: 0.
allNum: 0
}
}
componentDidMount() {
let {allPrice, allNum} = getAllFoodInfo();
this.setState({ allPrice, allNum})
myEvent.on('addcut', () => {
let {allPrice, allNum} = getAllFoodInfo();
this.setState({ allPrice, allNum})
})
}
}
myEvent.emit('addcut')