第09课:全局状态管理购物车

知识点:添加 mbox。

全局 mbox 组件

执行添加命令npm i --save mobx mobx-react添加我们后面用到的组件。

在之前的时候已经修改了 babelrc 了,这里提一下,修改配置文件 .babelrc让babelrc 支持 @ 修饰符。

npm i babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev
{
"presets": ["react-native"],
"plugins": ["transform-decorators-legacy"]
}

添加购物车

在 service 下添加 cart.js,将购物车的变量、业务逻辑都写在这里。

这里要将购物车分三个部分:第一部分在 App 入口处,调用购物车的初始化;第二部分在购物车内容变化时更新购物车内容;第三部分在购物车请求失败或者未登录的情况下清空购物车数据。

export default class {
    //购物车用到的数据
    data = extendObservable(this, {
        dis_count: 0,
        goods_amount: "0",
        goods_count: 0,
        is_in_bond: 0,
        list: [],
        total_count: 0,
        total_price: "0",
        total_reduce: "0"
    });
    //初次加载是否完成
    isLoaded = observable(false);
    //初始化
    init = action(() => {
        this.isLoaded = false;
        this.update();
    });
    //重设
    reset() {
        runInAction(() => {
            this.data.total_count = 0;
            this.data.total_price = "0";
            this.data.total_reduce = "0";
            this.data.list = [];
            this.isLoaded = true;
            this.data.goods_count = 0;
        })
    }
    //更新购物车内容
    update = action(async () => {

    })
}

这里实现了几个购物车的操作,基本是只需要执行修改数据的值即可,这里使用 runaction 的方法将请求之后的数据更新。

/**
     * 选中,取消选中
     */
    select = action(async (id, status) => {
        this.data.list.filter(item => item.id == id).map(item => item.select_status = status == 0 ? 1 : 0)
        //执行选中之后的请求
    });
    /**
     * 是否选中了所有
     */
    isSelectAll = observable(false);
    /**
     * 选中全部
     */
    selectAll = action(async () => {
        this.isSelectAll = !this.isSelectAll;
        let ids = [];
        this.data.list.map(item => {
            item.select_status = this.isSelectAll ? 1 : 0;
            ids.push(item.id);
            return item;
        });
        ids = ids.join(',');
        //执行全选操作        
    });
    /**
     * 数量减少
     */
    reduce = action(async (id, num) => {
        num = Number(num);
        num--;
        this.data.list.filter(item => item.id == id).map(item => {
            item.quantity = num
        })
        //减少数量
    });
    /**
     * 增加数量
     */
    plus = action(async (id, num, isBuyLimit, buyLimitNum, buyLimitMsg) => {
        num = Number(num);
        if (isBuyLimit == 1 && num >= buyLimitNum) {
            toast(`该商品为限购商品,${buyLimitMsg}`);
            return;
        }
        num++;
        this.data.list.filter(item => item.id == id).map(item => {
            item.quantity = num
        })
        //执行减少操作,注意出错的情况下回复原来的数量
    });
    /**
     * 直接设置数量
     */
    setNum = action((id, num) => {
        this.data.list.filter(item => item.id == id).map(item => {
            if (!item.old) item.old = item.quantity;
            item.quantity = num;
        })
    });
    /**
     * 改变数量
     */
    changeNum = action(async (id) => {
        let num = 0;
        let old = 0;
        this.data.list.forEach(res => {
            if (res.id == id) {
                num = res.quantity; old = res.old;
            }
        });
        if (num == old) return;
        //修改数量的请求
    });
    /**
     * 删除单个商品
     * @param {*} id 
     */
    del(id) {
        for (let i = 0; i < this.data.list.length; i++) {
            if (this.data.list[i].id == id) {
                return this.data.list.splice(i, 1);
            }
        }
    }
    /**
     * 删除部分商品
     */
    deleteGoods = action(async (ids) => {
        for (let id of ids) this.del(id);
        //删除的请求
    });
    /**
     * 删除所有
     */
    deleteAll = action(async () => {
        this.data.list.length = 0;
        //删除所有的请求
    });
    /**
     * 添加到购物车
     */
    addCart = action(async (id, num) => {
        this.data.total_count += num * 1;
        //添加的请求
        return this.data.total_count;
    });

实现购物车界面

修改/src/home/shop_cart.js,让 mbox 可以重新渲染购物车的界面。

export default observer(class extends React.Component {})

添加一个简单的头部组件,这个头部需要可以切换编辑和查看状态。

<Header
                showLeft={false}
                style={{
                    backgroundColor: "#fff",
                }}
                title={`购物车(${CartList.data.goods_count || 0})`}
                titleStyle={{
                    color: "#000"
                }}
                rightBtn={CartList.data.list.length == 0 ?
                    <Text allowFontScaling={false}
                        onPress={() => { }}
                        style={styles.headerRight}>编辑</Text>
                    : null
                }
            />

读取购物车组件中的商品数量,通过一个商品组件把商品全部渲染在页面上。

<FlatList
                keyExtractor={item => item.id}
                refreshing={this.state.refreshing}
                onRefresh={() => this.refresh()}
                ListEmptyComponent={() => {
                    if (CartList.data.list.length === 0) {
                        return <View style={styles.empty}>
                            <Text allowFontScaling={false} style={styles.empty_txt}>购物车没有商品哦</Text>
                            <TouchableOpacity activeOpacity={0.8} onPress={this.goHome.bind(this)}>
                                <View style={styles.empty_btn}>
                                    <Text allowFontScaling={false} style={styles.empty_btn_txt}>去首页看看</Text>
                                </View>
                            </TouchableOpacity>
                        </View>
                    } else {
                        return <View></View>
                    }
                }}
                data={CartList.data.list}
                renderItem={({ item }) => <GoodList
                    items={item}
                    editStatus={this.state.editStatus}
                    limitStock={this.limitStock}
                    editSelectArr={this.state.editSelectArr}
                    editSelect={this.editSelect.bind(this)}
                    goodsChangeQty={this.goodsChangeQty.bind(this)}
                    changeQty={this.changeQty.bind(this)}
                    goDetail={(id, sku) => this.goDetail(id, sku)}
                />
                }
            />

这个时候购物车是空的,我们可以顺便看看购物车此时的界面。

2df228f0-22b5-11e8-86f8-33ed71c2b78e

伪造购物车数据

在 cart.js 中修改 update 方法,将一些从线上接口拿到的数据填入这里。

update = action(() => {
    let list = [
         {
             total_count: 3, total_price: 384, list_type: 'goods',
             goods_list: [{
                 benefitMoney: '3.00',
                 buyLimitNum: 0,
                 can_select: 1,
                 goodsName: "MAC/魅可子弹头口红 Dangerous (3g )",
                 goodsShowDesc: '[MAC]MAC/魅可子弹头口红 Dangerous (3g )',
                 goodsShowName: '美国·抢镜的必备法宝',
                 goods_price: '384.00',
                 id: 1,
                 image: 'http://img4.daling.com/data/files/mobile/2017/11/30/15120349684184.jpg_300x300.jpg',
                 isBuyLimit: 0,
                 isForeignSupply: 1,
                 isInBond: 1,
                 limitStock: 400,
                 marketPrice: '170.00',
                 quantity: 3,
                 salePrice: "128.00",
                 select_status: 0
             }]
         }
     ]
     this.data.total_count = 3;
     this.data.total_price = "0";
     this.data.total_reduce = "0";
     this.data.list = list;
     this.isLoaded = true;
     this.data.goods_count = 0;
 })

在 App 启动之后立即执行购物车的初始化方法,后面会在这里做用户身份判断等操作。

src/index.js的页面渲染完成之后做购物车初始化,同时将假数据插入到正常的数据中。

componentDidMount(){
    CartList.init();
 }

购物车的商品

这里将购物车的商品单独成一个组件,这也是最常见的开发方式,可以一个组件多处使用,也可以简化页面的逻辑。

新建一个 class,将商品的数据传到组件里,这里先写一个文字看看样子。

//商品的样式
class GoodList extends React.Component {

    render(){
        return <View>
            <Text>测试</Text>
        </View>
    }
}

这里把商品详情的 UI 贴出来,内容比较简单没有什么优化,想要自己写的可以重新做一套。

<View style={styles.goods_main}>
            <View style={styles.goods_list}>
                {/*选中的按钮*/}
                {this.props.editStatus
                    ? <View style={styles.operatingBtn}>
                        <TouchableOpacity activeOpacity={0.8}
                            style={styles.operatingBtnBox}
                            onPress={this.props.editSelect.bind(null, this.props.items.id)}>
                            {this.props.editSelectArr.indexOf(this.props.items.id) == -1
                                ? <Image source={{ uri: require('../images/tab-shopping-cart-select') }}
                                    resizeMode='cover'
                                    style={{ width: px(34), height: px(34) }} />
                                : <Image source={{ uri: require('../images/tab-shopping-cart-selected') }}
                                    resizeMode='cover'
                                    style={{ width: px(34), height: px(34) }} />
                            }
                        </TouchableOpacity>
                    </View>
                    : <View style={styles.operatingBtn}>
                        {this.props.items.limitStock > 0 && this.props.items.can_select == 1
                            ? <TouchableOpacity activeOpacity={0.8}
                                style={styles.operatingBtnBox}
                                onPress={CartList.select.bind(null, this.props.items.id, this.props.items.select_status)}>
                                {this.props.items.select_status == 0
                                    ? <Image source={{ uri: require('../images/tab-shopping-cart-select') }}
                                        resizeMode='cover'
                                        style={{ width: px(34), height: px(34) }} />
                                    : <Image source={{ uri: require('../images/tab-shopping-cart-selected') }}
                                        resizeMode='cover'
                                        style={{ width: px(34), height: px(34) }} />
                                }
                            </TouchableOpacity>
                            : null}
                    </View>
                }
                {/*商品图*/}
                <View style={styles.goods_img}>
                    <TouchableOpacity onPress={() => this.props.goDetail(this.props.items.id, this.props.items.sku)}>
                        <Image source={{ uri: this.props.items.image }}
                            style={styles.img}
                            resizeMode='cover'
                        />
                        {this.props.items.limitStock == 0
                            ? <View style={styles.goods_img_cover}>
                                <Text allowFontScaling={false} style={styles.goods_img_txt}>已抢光</Text>
                            </View>
                            : null
                        }
                        {this.props.items.limitStock > 0 && this.props.items.limitStock < 10
                            ? <View style={styles.goods_limit}>
                                <Text allowFontScaling={false} style={styles.goods_limit_txt}>仅剩{this.props.items.limitStock}件</Text>
                            </View>
                            : null
                        }
                    </TouchableOpacity>
                </View>
                {/*商品名称价格等*/}
                <View style={styles.goods_content}>
                    <TouchableOpacity onPress={() => this.props.goDetail(this.props.items.id, this.props.items.sku)}>
                        <Text allowFontScaling={false}
                            style={[styles.goods_name, this.props.items.limitStock == 0 || this.props.items.can_select == 0 ? styles.color_disabled : '']}
                            numberOfLines={2}>
                            <Text allowFontScaling={false}>{this.props.items.goodsShowDesc}</Text>
                        </Text>
                    </TouchableOpacity>
                    <View style={styles.operating}>
                        <Text allowFontScaling={false}
                            style={[styles.money, this.props.items.limitStock == 0 || this.props.items.can_select == 0 ? styles.color_disabled : '']}>
                            ¥{this.props.items.salePrice}
                        </Text>
                        <Text allowFontScaling={false}
                            style={[styles.quantity, this.props.items.limitStock == 0 || this.props.items.can_select == 0 ? styles.color_disabled : '']}>
                            x{this.props.items.quantity}
                        </Text>
                        {this.props.items.limitStock > 0 && this.props.items.can_select != 0
                            ? <View style={styles.operatingBox}>
                                {this.props.items.quantity == 1
                                    ? <TouchableOpacity
                                        activeOpacity={0.8}>
                                        <View style={styles.reduce}>
                                            <Text allowFontScaling={false} style={[styles.btn1, styles.color_disabled1]}>-</Text>
                                        </View>
                                    </TouchableOpacity>
                                    : <TouchableOpacity
                                        activeOpacity={0.8}
                                        onPress={CartList.reduce.bind(null, this.props.items.id, this.props.items.quantity)}>
                                        <View style={styles.reduce}>
                                            <Text allowFontScaling={false} style={[styles.btn1]}>-</Text>
                                        </View>
                                    </TouchableOpacity>
                                }
                                <View style={styles.inpBox}>
                                    <TextInput allowFontScaling={false}
                                        style={styles.inp1}
                                        defaultValue={String(this.props.items.quantity)}
                                        keyboardType="numeric"
                                        onChangeText={(txt) => this.props.goodsChangeQty(this.props.items.id, txt)}
                                        onBlur={(event) => this.props.changeQty(this.props.items.id)}
                                        autoFocus={false}
                                        underlineColorAndroid="transparent">
                                    </TextInput>
                                </View>

                                <TouchableOpacity
                                    activeOpacity={0.8}
                                    onPress={CartList.plus.bind(null, this.props.items.id, this.props.items.quantity, this.props.items.isBuyLimit, this.props.items.buyLimitNum, this.props.items.buyLimitMsg)}>
                                    <View style={styles.plus}>
                                        <Text allowFontScaling={false} style={[styles.btn1]}>+</Text>
                                    </View>
                                </TouchableOpacity>
                            </View>
                            : null
                        }
                    </View>
                </View>
            </View>
        </View>

底部组件

底部浮动放着当前购物车的价格、数量、下单等信息,我们把这个地方也做成一个组件,放在 Flatlist 的后面即可。

{CartList.data.list.length == 0
     ? null
     : <Footer
         editStatus={this.state.editStatus}
         total_count={CartList.data.total_count}
         total_price={CartList.data.total_price}
         selectAllStatus={CartList.isSelectAll}
         editSelectAllStatus={this.state.editSelectAllStatus}
         editSelectArr={this.state.editSelectArr}
         selectAllFn={CartList.selectAll}
         editSelectAllFn={this.editSelectAllFn.bind(this)}
         delete={this.delete.bind(this)}
         submit={this.submit.bind(this)}
     />
 }

这里写一个简单的选择器 + 合计价格 + 提交按钮。

<View style={styles.footer}>
            <View style={styles.operatingBtn}>
                <TouchableOpacity activeOpacity={0.8}
                    style={styles.operatingBtnBox}
                    onPress={()=>this.props.selectAllFn()}>
                    {this.props.selectAllStatus
                        ? <Image source={{ uri: require('../images/tab-shopping-cart-selected') }}
                            resizeMode='cover'
                            style={{ width: px(34), height: px(34) }} />
                        : <Image source={{ uri: require('../images/tab-shopping-cart-select') }}
                            resizeMode='cover'
                            style={{ width: px(34), height: px(34) }} />
                    }
                </TouchableOpacity>
            </View>
            <View style={styles.footerContent}>
                <Text allowFontScaling={false} style={[styles.footerContentTxt0, styles.footerContentTxt1]}>全部</Text>
                <Text allowFontScaling={false} style={styles.footerContentTxt1}>合计</Text>
                <Text allowFontScaling={false} style={styles.footerContentTxt2}>¥{this.props.total_price}</Text>
                <TouchableOpacity activeOpacity={0.8} onPress={this.props.submit}>
                    <View style={[styles.submit, this.props.total_price > 0 ? '' : styles.submitDisabled]}>
                        <Text allowFontScaling={false} style={styles.submit_txt}>去结算({this.props.total_count})</Text>
                    </View>
                </TouchableOpacity>
            </View>
        </View>

5b40ca60-22c3-11e8-86f8-33ed71c2b78e

添加购物车事件

界面开发已经到此结束,接下来把购物车的编辑、改数量等操作补充完善就好了。

先搞定编辑的情况,使用一个状态值控制购物车的编辑和完成的状态,这个值同时控制顶部的按钮、商品内部事件的触发、底部的按钮组,使用一个数组记录当前选择的商品,如果点击删除,则把数组的 ID 传入购物车的删除方法里,调用接口之后要刷新购物车列表。

//进入编辑状态
    edit() {
        this.setState({ editStatus: true })
    }
    //退出编辑状态
    done() {
        this.setState({ editStatus: false })
    }
    //编辑选中商品
    editSelect(index) {
        let item = CartList.data.list[index];
        if (this.state.editorArr.indexOf(item.id) < 0) {
            this.state.editorArr.push(item.id);
        } else {
            this.state.editorArr.splice(this.state.editorArr.indexOf(item.id), 1)
        }
        let editSelectAllStatus = false;
        if (this.state.editorArr.length === CartList.data.list.length) {
            editSelectAllStatu = true;
        }
        this.setState({ editorArr: this.state.editorArr.concat(), editSelectAllStatus })
    }
    //删除
    delete() { 
        CartList.deleteGoods( this.state.editorArr.join(','))
    }
    //编辑选中所有商品
    editSelectAllFn() {
       if (this.state.editSelectAllStatus) {
            this.setState({ editorArr: [], editSelectAllStatus: false })
        } else {
            let list = []
            CartList.data.list.forEach(item => {
                list.push(item.id)
            })
            this.setState({ editorArr: list, editSelectAllStatus: true })
        }
    }

点击商品要跳转到商品详情页,点击回到首页要跳转到首页。

//跳转到商品详情
    goDetail(id, sku) {
        this.props.navigation.navigate('Goods', {
            id: sku ? '' : id,
            sku: sku
        });
    }
    //跳到首页
    goHome() { 
        this.props.navigation.navigate('Home');
    }

购物车的操作基本上依赖于 cart.js 和后端接口的数据,这里仅提供几个方法研究。

//修改数量
    goodsChangeQty(id, num) {
        CartList.setNum(id, Number(num));
    }
    //选中商品
    select(id,status){
        CartList.select(id,status);
    }

简单的购物车基本上就结束了,如果有特殊需要可以单独改逻辑实现自己需要的效果。

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页