美文网首页我爱编程
React开发实践--2实现一个类似客户端的商品轮播图阅览交互

React开发实践--2实现一个类似客户端的商品轮播图阅览交互

作者: 继续海阔天空 | 来源:发表于2018-05-27 15:16 被阅读104次

    写在前面

    这篇仍然并不会也不打算去介绍或者科普React框架的基本知识点,但是会在行文中有很多涉及。

    怎样实现的思路

    一个电商APP, 要想让他的web HTML5 体验更接近客户端本身还是有很大的挑战的。怎样做出一些更符合客户端用户操作习惯的交互来,是前端开发中很重要的一个点。

    我想要做的是一个类似客户端的商品轮播图阅览交互,总体来说,他的主要逻辑是:

    1. 在商品详情页显示商品轮播图
    2. 点击任意一张商品轮播图,能够全局浏览商品轮播图
    3. 通过左划右滑手势可是在全屏状态下阅览商品轮播图
    4. 点击返回键,退出全屏,回到商品详情页

    有赖于react-photoswipe 这个库的支持,它已经将前三项做得很好了。我希望做的其实是第四项。细化需求,它不仅仅要求能够回到商品详情页,还希望能够最大限度地优化性能,最好的体验就是像客户端那样。

    这里,我采取的措施是,将我集成了react-photoswipe 这个库的组件放到</ProductDetail /> 组件之中。

    <ProductCarousel images={product.images} alt = {product.name} />
    

    接下来,我的想法是这样的:

    当用户在商品详情页点击任意一张商品轮播图时,页面上全屏显示那张商品轮播图,同时,当前的路由发生改变,由/products/:productId/products/:productId/showpic,这样,当用户点击返回的时候,就能够实现从当前路由/products/:productId/showpic返回到了/products/:productId

    但是,这个时候,我遇到了很多与React-router有关的坑,其实与其说是坑,倒不如说是知识上的不足。当我把这些知识补足之后,再来看,其实并不难了。

    React-router

    第一个问题: exact

    很明显,第一个问题是关于React-routerapp.js 中的配置的。前面介绍了我的总体思路,这个思路的一个关键点是当路由发生改变的时候,</ProductDetail />组件不应该unmount 或者重新didmount, 这样才能够让这种轮播图阅览交互更像是客户端的体验。

    这时候,用到的第一个知识点是React-router的exact 配置。这本身并不是什么难点,我在这里只是想说明它的这一妙用。

    由于我们大部分的React-router的匹配都采用了exact的匹配,在这个时候,我把原来对商品详情页的exact匹配去掉了,使得能够实现前述我的思路。

    第二个问题: </ProductDetail />组件的生命周期

    前面说了,我们在这个项目中,为了优化前端性能,使用了动态加载,来进行split coding。这个时候,我发现,如果使用了动态加载,我的</ProductDetail />组件每次进入轮播图全屏页面时,都会因为进入到了下一个生命周期,而不能进入轮播图全屏页。

    这个问题出在哪里呢?原来这个问题是由于React-router以及它所关联的React生命周期造成的。

    我以前有个误区,不知道各位读者是否也会存在这样的误区。就是React简单地认为它是单向数据流,除非父组件传递給子组件能够改变父组件本身的属性,否则,子组件的更新,不会引起父组件的更新甚至进入下一个生命周期。

    但是,当然没有那么简单。

    还是以我上面的问题为例,为什么会出现那样的问题呢?原因是:当一开始进入轮播图详情页面时,在我的<ProductCarousel />这个子组件发生了变化,他引起了路由的变化。由于我的路由管理由React-router完成,实际上顶部的<Switch>也相应地发生了更新,甚至可以这么说,<Switch>组件也必须发生更新,只有这样,他才有存在的意义。而紧接着,由于我们对动态引入的错误使用,造成了</ProductDetail />组件被迫重新装载,而他的重新装载又直接造成了他进入下一个生命周期。

    关于我们是怎样错误使用动态引入的,以后会详细来解释。

    这里,还是针对我刚刚提到的误区来谈一谈。所以,还是说回来,这当然不能算是坑,这是对知识理解的不深刻。

    看到这个问题之后,我最初想到的是: 如果路由改变了,而加载的组件(import)并没有改变,就不更新这个组件的状态

    但是,马上就发现了问题,还是这个例子:路由从/products/:productId/products/:productId/showpic发生了改变,这个时候我们没有改变</ProductDetail />组件的状态,则他的属性(location,props)较之前并没有发生改变。这本身就不是正常的,也就会从根本上造成虽然一个问题解决了,但是总还有新的问题产生。为什么呢?因为你没有按照规律办事啊!

    React-router的思想是什么呢?其中一个思想就是组件上的React-router赋予的属性(location,history )是与实际相一致的。但是刚才,我试图通过调用 componentWillReceiveProps方法,来实现阻止组件更新的目的。

    结果造成什么问题了呢?

    一开始,我点击轮播图,路由切换,打开轮播图,没有问题。可是当我点击返回键的时候,由于我前面的判断:如果路由改变了,而加载的组件(import)并没有改变,就不更新这个组件的状态,导致没有退回到商品详情浏览页。这当然是不正常的了。

    我们应该顺应React-router的设计思想,最后采取的办法是:如果路由改变了,而加载的组件(import)并没有改变,这个装载的组件不变,但他的属性(location,history)要相应地发生改变,他必须仍然在原有的生命周期中

    可能听起来,还是有点抽象,还是直接上代码:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    import {BrowserRouter, Route, Switch, Redirect} from 'react-router-dom';
    import BaseLayout from './BaseLayout';
    import Loading from 'react-loading';
    import Loadable from 'react-loadable';
    
    
    const LoadingComponent = props => {
    // something 
    };
    
    
    class RouteWithLayout extends React.Component {
        state = {
            loader: () => {},
            exportName: null,
            LoadableComponent: null
        }
    
        static getDerivedStateFromProps(nextProps, prevState) {
           
            if (nextProps.loader.toString() === prevState.loader.toString() && nextProps.exportName === prevState.exportName) return null;
            const { loader, exportName } = nextProps;
            const loadableOpts = {
                loader,
                loading: LoadingComponent
            };
    
            if (exportName) {
                loadableOpts.render = (loaded, props) => {
                    const Component = loaded[exportName];
                    return <Component {...props} />;
                };
            }
            return {
                loader,
                exportName,
                LoadableComponent: Loadable(loadableOpts)
            };
        }
    
        render() {
            const { hideFooter, hideReturnTop, ...rest} = this.props;
            return (
                <Route {...rest} render={matchProps => (
                    <BaseLayout hideFooter={hideFooter} hideReturnTop={hideReturnTop} {...matchProps}>
                        <this.state.LoadableComponent {...matchProps} />
                    </BaseLayout>
                    )}
                />
            );
        }
    }
    
    

    第三个问题: 匹配不匹配

    我们知道,在客户端的图片阅览里,实际上,是不能直接通过访问地址的方式,来全屏浏览商品轮播图的,这是一个更深层次的交互设计,因此我在</ProductDetail />组件的componentDidMount 方法中写下了这样的代码

            if (!this.props.match.isExact) {
                this.props.history.replace(this.props.match.url);
            }
    

    如此依赖,当初次载入</ProductDetail />组件时,一律显示商品详情页面。

    最后

    最终的实现效果,前面提到的指标都已经完成了。也能够比较完美地使用动态加载来加载组件了。

    相关文章

      网友评论

        本文标题:React开发实践--2实现一个类似客户端的商品轮播图阅览交互

        本文链接:https://www.haomeiwen.com/subject/abpndftx.html