关于服务端渲染响应式页面的思考

319 阅读2分钟

一般使用场景

1、在tailwind.config.ts配置小于等于1023px为mobile端,大于等于1024px为pc端

//~ tailwind.config.ts
const config: Config = {
    ...
    theme: {
        ...
        screens: {
          mobile: { max: '1023px' },
          pc: { min: '1024px' },
        },
    }
    ...
}

2、在业务组件里面使用如下图

通过上述步骤,能达到pc端与mobile端表现不同样式的效果,但如果某一块样式pc端与mobile端差异很大,这时候通过这种方式就很难实现了。

特殊使用场景

方案一

遇到前面提到的场景,我们通常会写2套组件,pc端1套,mobile端1套,然后再通过判断用户当前使用的设备再去加载相应组件。

import { useMediaQuery } from 'react-responsive';
import A from '../A.tsx'
import B from '../B.tsx'

const Test = () => {
  const isPC = useMediaQuery({ minWidth: 1024 });
  
  return isPC ?<A/> : <B/>;
}

这样貌似很完美,但存在3个问题:

  1. 服务端无法使用hooks,传输到客户端的html组件A与组件B渲染的内容都不会存在
  2. 在本地开发环境会报水合错误,因为服务端渲染的内容与客户端渲染的内容差异较大
  3. 不利于seo优化,如果组件A与组件B存在重要内容给搜索引擎爬取,而这部分内容传下载下来却丢失了

方案二

前面我们是在客户端判断设备类型,因此存在问题,那我们将判断设备类型这一步放到服务端去做呢

import A from '../A.tsx'
import B from '../B.tsx'

const MOBILE_REG = '.*iPhone|Android|SymbianOS|Windows Phone|iPod{1,}.*';

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { params } = context;
  const ua = context.req.headers['user-agent'];
  let isMobile = true;
  if (ua && ua.match(MOBILE_REG)) {
    isMobile = true;
  } else {
    isMobile = false;
  }

  return {
    props: {
      isMobile,
    },
  };
};

const Test = ({isMobile}) => {
   return isMobile ?<B/> : <A/>;
}

这样做解决了服务端返回html中组件内容为空的问题,但仅适用于服务端渲染,静态生成在构建时就生成页面了,这时显然无法判断设备类型。

方案三

如果将组件A与组件B的内容都生成下来,再通过display去控制是否展示其内容,这样前面提到的问题就都得到解决了。

import A from '../A.tsx'
import B from '../B.tsx'

const PC: React.FC<{ children: React.ReactElement }> = ({ children }) => {
  return React.cloneElement(children, {
    ...children.props,
    className: `${children.props.className || ''} pc:block mobile:hidden`,
  });
};

const Mobile: React.FC<{ children: React.ReactElement }> = ({ children }) => {
  return React.cloneElement(children, {
    ...children.props,
    className: `${children.props.className || ''} pc:hidden mobile:block`,
  });
};

const Test = () => {
    return (
        <>            
          <PC>
              <A/>
          </PC>
           <Mobile>
              <B/>
          </Mobile>
        </>
    )
}

这样做唯一的坏处是首次就加载了pc端与mobile端的代码,但总体来说好处多于坏处:

  1. 解决了报水合错误的问题
  2. 无论是pc端的内容还是mobile端的内容都加载了进来,搜索引擎能够爬取所有内容
  3. 能同时支持静态生成与服务端渲染