页面布局

这里提供的布局只是一种布局思路或者说策略,需要根据实际项目进行调整。

页面分割为三块内容,头部-header,填充内容- page-content,底部-footer,这三部分都不需要使用定位就可以固定在顶部和底部,这样就不会有软键盘弹出时fixed失效的问题。可以滚动的只有填充内容- page-content部分。

根元素的样式:

#root {
  height: 100vh;
  width: 100vw;
  background-color: $ed-page-bgcolor-primary;
  font-size: pxToRem(16);
  font-family: $fontFamily;
  overflow: hidden;
  position: relative;
}

需要注意的是,根元素设置了高度(屏幕高度),宽度(屏幕宽度),定位,以及元素溢出屏幕时隐藏而不是滚动。

头部-header的样式:

.header {
  width: 100vw;
  height: size(45);
}

修改header的高度时,需要在.page-content和.page-content.no-footer样式中修改height减去的高度。

.footer {
  height: size(45);

  .footer-menu-active {
    color: #FFF;
  }
}

修改footer的高度时,需要在.page-content样式中修改height属性减去的高度。

填充内容- page-content的样式:

.page-content {
  position: relative;
  background-color: #FFF;
  height: calc(100vh - 45px - 45px);
  overflow-y: scroll;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch
}

.page-content.no-header {
  height: calc(100vh - 45px);
}

.page-content.no-footer {
  height: calc(100vh - 45px);
}

只需要头部添加类名header ,填充内容添加类名page-content,根据是否有头部和底部添加对应的类名,底部添加类名footer,这个布局就完成了。

page-content的高度=屏幕高度-头部高度-底部高度,这样保证三块内容高度相加等于根元素的高度且不溢出,page-content设置了Y轴方向的滚动,同时具有position属性和高度,所以其子元素除了fixed定位外的所有定位不会是body和根元素,子元素使用百分比设置高度也会生效。需要X轴滚动的情况,在滚动区域外加一层div,不建议修改这个类。

样式上定义了没有头部时page-content的高度和没有底部时page-content的高度,没有头部也没有底部的页面,请不要用这个高阶组件,自己定义一个样式吧,当然,添加一个.page-content.no-header.no-footer的类名也可以。

关于适配样式

适配需要留出空白的部分,只需要调整根元素的高度或者内边距,和填充内容的高度就可以完成,以IPhoneX适配为例,需要在底部留出20px的高度,这个时候只需要让根元素的高度和填充内容page-content的高度都减少20px就可以了。

#root.adapt-iosX {
  height: calc(100vh - 20px);

  .page-content {
    height: calc(100vh - 45px - 45px - 20px);
  }
  
  .page-content.no-header {
    height: calc(100vh - 45px - 20px);
  }

  .page-content.no-footer {
    height: calc(100vh - 45px - 20px);
  }
}

使用高阶组件

如果每个页面都需要手动添加这些样式是比较麻烦的,尤其是当需要修改或者添加一些公共类名的时候,所以有了高阶组件PublicPage.jsx,PublicPage组件内头部显示内容使用的Header组件,底部显示内容使用的EdTabBar组件,填充内容显示的是传进来的组件,头部显示内容和底部显示内容可以根据实际项目需要进行修改。

显示的控制:UI库中,header默认是一定会显示的,后续可能会做一些优化,把头部也变成通过读取配置控制显示,底部是根据传参footerKey,读取底部内容的配置footerConfig,获取底部显示内容(如问文字,图标,路由)。

参数传递:像是Header组件需要传入title和backFn,为了PublicPage组件能够简便的从传入的组件中获取参数,除了函数外,都是从静态变量中获取,比如

// 页面,LayoutFlex就是高阶组件接收参数中的WrappedComponent
class LayoutFlex extends Component {
  static title = 'flex公共布局';

  constructor(props) {
    super(props);
  }
  ...
}
// 高阶组件
export default (WrappedComponent, pageParam) => {
  class PublicPage extends Component {
    ...
    render(){
      // WrappedComponent.title就可以获取到LayoutFlex设置的静态变量title
      const title = WrappedComponent.title || pageParam.title || '';
    }
  }
  return PublicPage;
}

为什么不通过静态变量的方式获取组件内的函数呢?因为静态变量无法获取到组件任何的props和state,所以使用了通过ref的方式,如下:

// 页面,LayoutFlex就是高阶组件接收参数中的WrappedComponent
class LayoutFlex extends Component {
  static title = 'flex公共布局';

  constructor(props) {
    super(props);
  }
  
  backFn = () => {
    console.log('backFn')
  };
  
  ...
}
// 高阶组件
export default (WrappedComponent, pageParam) => {
  class PublicPage extends Component {
    constructor(props) {
      ...
      this.wrappedRef = React.createRef();
    }
    
    backFn = () => {
      this.wrappedRef.current.back();
    }
    
    ...
    
    render(){
      <WrappedComponent ref={this.wrappedRef} />
    }
  }
  return PublicPage;
}

但是,ref这种方式不适用于函数声明的组件,所以高阶组件还接收一个参数对象pageParam,pageParam的属性名与组件设置的静态变量一致,且行为一致,函数声明的组件需要设置pageParam.isFunComponent = true,此时不会创建ref,且Header组件的backFn和rightFn触发时执行 window.history.back()。

组件静态变量说明:

属性名

作用

title

Header组件的children,默认值为空

isGoback

Header组件isGoback的属性值,默认为true

right

Header组件right的属性值,无默认值

footerKey

底部配置footerConfig的key,若footerKey或footerConfig[footerKey]布尔值为false,则页面不显示底部

高阶组件内还可以做什么?

这个高阶组件可能拿到项目里还需要少量更改,因为引入了Header和EdTabBar这两个组件,大多数时候替换头部和底部为项目自己的组件就可以了。

PublicPage.jsx文件内的footerConfig,是底部显示内容的配置,需要显示底部的页面只需要设置footerKey就可以了。

高阶组件内还添加了componentDidCatch声明周期,用于捕获页面级别的错误,同样的,只需要在高阶组件内写一遍就可以了。

在AppRouter中可以看到有两个路由配置,区别是RouterHOCConfig中的页面会使用高阶组件,RouterConfig中的页面不会使用高阶组件,为了使用方便,封装了Router,代码如下:

const Router = (props) => {
  const { path, exact = true, component, pageParam = {} } = props;
  return (
    <Route path={path} exact={exact} component={PublicPage(component, pageParam)} />
  );
};

export default Router;

这样页面就和平时开发的就没有区别了,只需要设置一些静态变量就可以,而且不需要每个页面引入Header组件。

Last updated