关于react.js:React-Hook-和-SCSS-结合的响应式布局方案

51次阅读

共计 6596 个字符,预计需要花费 17 分钟才能阅读完成。

背景

公司中有多个我的项目须要同时开发 PC 端和 H5 端,大部分中央逻辑和交互比拟相似,次要是款式上有些区别。为了更好地复用代码、进步开发效率,通过一段时间的实际后,咱们总结出这套 React Hook 和 SCSS 联合、px 和 vw 共存的响应式布局计划。

根底代码

创立我的项目

首先,咱们来创立一个我的项目,这里我用的是 Create React App,抉择了 typescript 模板,通过以下命令即可创立我的项目:

npx create-react-app my-app --template typescript

逻辑局部

逻辑这块,次要思路是通过写一个 Hook,用于检测以后视口是否为挪动端大小,并依据视口大小实时更新返回值,这样咱们在组件中须要的时候间接调用这个 Hook 就行了,十分不便。

首先,App.tsx 负责实时检测是否挪动端,并且将该值通过 Context 传递给所有子组件和后辈组件,代码如下:

import {createContext, useState, useEffect} from 'react';
import MyComponent from './components/MyComponent/MyComponent';

// 判断以后视口是否为挪动端大小
export const checkIsMobile = () => {return window.innerWidth <= 620}
// 保留是否挪动端状态的 context
export const IsMobileContext = createContext<boolean>(false)

function App() {const [isMobile, setIsMobile] = useState(checkIsMobile)
  useEffect(() => {const resizeHandler = () => {const currentIsMobile = checkIsMobile()
      setIsMobile(currentIsMobile)
    }
    // 监听 window 的 resize 事件,窗口大小扭转时从新计算 isMobile 的值
    window.addEventListener('resize', resizeHandler)
    // 组件销毁时勾销事件监听
    return () => window.removeEventListener('resize', resizeHandler)
  }, [])

  return (
    // 通过 IsMobileContext 将 isMobile 的值传递给所有子组件
    <IsMobileContext.Provider value={isMobile}>
      <MyComponent/>
    </IsMobileContext.Provider>
  );
}

export default App;

而后咱们创立 useIsMobile.ts 这个文件,这外面次要保留咱们的 useIsMobile hook,用于在组件中响应式获取 App.tsx 中传递下来的 isMobile 值,代码很简略:

// 从 IsMobileContext 中获取以后是否挪动端状态的 hook
export const useIsMobile = () => {const isMobile = useContext(IsMobileContext)
  return isMobile
}

最初,咱们将 useIsMobile 导入到 MyComponent 中应用,在 PC 端显示 “pc”,在挪动端显示 “mobile”:

const MyComponent = () => {const isMobile = useIsMobile()

  return (<div className={styles.myComponent}>{isMobile ? 'mobile' : 'pc'}</div>
  )
}

能够看到,咱们的 React 组件曾经能够实时获取 isMobile 的值:

款式局部

咱们通过 npm install sass 命令装置 sass,用于将咱们的 SCSS 代码编译成 CSS。

postcss-px-to-viewport 很好,如果只是开发 H5 我的项目,那我举荐你用这个工具。然而咱们常常会遇到 px 和 vw 共存的状况,比方须要防止 PC 端字体和元素大小绝对 H5 等比例放大的时候,咱们不能简略地将全副 px 转成 vw,而是抉择在 PC 端次要应用 px,在 H5 端次要用 vw。

咱们最终抉择的计划是在挪动端通过以下函数将 px 转成 vw,$px 为设计稿中 px 值的大小,$base 为设计稿宽度,这里默认为 375,可依据我的项目状况自行批改:

@function px2vw($px, $base: 375px) {@return calc($px / $base) * 100vw;
}

我之所以没有抉择 rem,是因为 vw 更直观,当初的兼容性也十分好,并且不须要像 rem 那样额定引入脚本来动静设置 html 元素的字体大小。

接下来咱们在 MyComponent 中增加一个 div,并且在挪动端给它设置宽高和字体大小等属性,以查看 px2vw 函数的成果:

<div className={[styles.myComponent, isMobile && styles.isMobile].filter(Boolean).join(' ')}>
  <div className={styles.box}>{isMobile ? 'mobile' : 'pc'}</div>
</div>

对应的款式文件 MyComponent.module.scss 代码如下:

@import "../../styles/function.scss";

.isMobile {
  .box {width: px2vw(300px);
    height: px2vw(300px);
    font-size: px2vw(35px);
    background-color: #0099CC;
  }
}

从图中能够看到,当页面大小切换到 H5 的宽度后,即便持续放大视口宽度,咱们的容器与视口宽度的比例始终放弃不变,通过查看元素也能够看到 width 等属性的值曾经被转换为 vw

优化开发体验

下面的步骤曾经实现了咱们想要的成果,在须要写挪动端款式的组件中应用 useMobile Hook 能够实时获取以后是 PC 还是挪动端,而后通过给 DOM 动静增加 className,即可为 PC 和挪动端编写不同的款式。

然而在开发体验上还有很多能够优化的中央,上面让咱们一起逐渐优化它。

对立增加 className

在下面的 MyComponent 组件中,咱们在挪动端给外层 div 增加了 isMobile 这个 className,而后在 SCSS 中应用这个选择器来编写挪动端代码。如果每个组件都这样写,那就太麻烦了。

咱们能够对立在 App 中为 body 元素增加 pcmobile 这个 class,而后在各个组件中就不必再独自增加了,须要写挪动端款式的中央间接用 :global(.mobile) {...} 即可。

App.tsx 中增加如下代码:

useLayoutEffect(() => {
  type BodyClassName = 'pc' | 'mobile'
  const bodyClass: BodyClassName = isMobile ? 'mobile' : 'pc'
  const classToRemove: BodyClassName = bodyClass === 'mobile' ? 'pc' : 'mobile'
  document.body.classList.remove(classToRemove)
  document.body.classList.add(bodyClass)
}, [isMobile])

这里的逻辑很简略,监听 isMobile 值的变动,而后动静切换 body 元素的 className 即可。须要留神的是,这里用的是 useLayoutEffect,而不是 useEffect,否则在挪动端首次加载的时候页面会先绘制 PC 端的款式,随后才立刻绘制挪动端款式,从而导致页面闪动,对于 useLayoutEffect,如果不理解的同学能够参考 官网文档。

而后把 MyComponent 组件中对于 className 的判断去掉:

<div className={styles.myComponent}>
  <div className={styles.box}>{isMobile ? 'mobile' : 'pc'}</div>
</div>

再略微革新一下款式文件 MyComponent.module.scss:

@import "../../styles/function.scss";

:global(.mobile) {
  .myComponent {
    .box {width: px2vw(300px);
      height: px2vw(300px);
      font-size: px2vw(35px);
      background-color: #0099CC;
    }
  }
}

当初的代码成果跟革新之前是一样的,然而 JSX 局部看起来更洁净、写起来更不便了,在任意组件中须要写挪动端款式的时候只须要将代码增加到 :global(.mobile) {...} 选择器中即可。

px2vw 函数主动导入

当初咱们的 MyComponent.module.scss 顶部有这样的一行代码:@import "../../styles/function.scss"; 用于导入 px2vw 函数,每个须要用到该函数的 SCSS 文件中都要引入这个 function.scss,并且因为咱们应用的是相对路径,援用门路依据组件所在位置不同也会不一样,这也是一件比较烦人的事,接下来咱们看看怎么通过批改 webpack 配置去解决这个问题。

装置 craco

通过 npm install -D @craco/craco @craco/types 命令装置 craco,用于批改 webpack 配置。

需将 package.json 文件 scripts 模块中的所有 react-scripts 改成 craco:

"scripts": {
-  "start": "react-scripts start"
+  "start": "craco start"
-  "build": "react-scripts build"
+  "build": "craco build"
-  "test": "react-scripts test"
+  "test": "craco test"
}

配置门路别名

在我的项目根目录下创立 craco.config.js,内容如下:

const path = require('path')

module.exports = {
  webpack: {configure: (webpackConfig) => {webpackConfig.resolve = webpackConfig.resolve || {}
      webpackConfig.resolve.alias = webpackConfig.resolve.alias || {}
      webpackConfig.resolve.alias['@'] = path.resolve(__dirname, 'src')
      return webpackConfig
    }
  }
}

这里通过 webpack 的 resolve.alias 为 src 目录设置了 @ 这个别名,这样咱们在任意 SCSS 文件中都能够通过 @import "@/styles/function.scss"; 引入 px2vw 函数了,而不须要关怀组件所处的地位。

function.scss 文件主动导入

让咱们再批改一下 craco.config.js,增加 style 对象:

const path = require('path')

module.exports = {
  style: {
    sass: {
      loaderOptions: {
        // 全局增加 scss 前缀代码
        additionalData: (content, loaderContext) => {const { resourcePath, rootContext} = loaderContext
          // 以后文件的相对路径
          const relativePath = path.relative(rootContext, resourcePath)
            .split(path.sep)
            .join('/')
          // 待引入的文件
          const filesToImport = ['src/styles/function.scss']

          if (/\.scss$/.test(relativePath) && !filesToImport.includes(relativePath)) {
            // 如果以后文件后缀名为 .scss,则在文件结尾增加须要引入的文件
            const importStatements = filesToImport
              .map(file => `@import "${file.replace('src','@')}";`)
              .join('\n')
            return `${importStatements}\n${content}`
          } else {return content}
        },
      }
    },
  }
}

在这里,咱们利用了 sass-loader 的 additionalData 配置来实现 function.scss 文件的主动导入。须要留神的是,应用 additionalData 主动导入 SCSS 文件时,要防止导入蕴含代码输入的文件,否则会导致反复打包,反复生成雷同的 CSS 代码。

当初咱们就能够把咱们组件款式文件中的 function.scss import 语句删掉了,开发体验失去进一步晋升。

px2vw 智能提醒、一键增加调用

当初还有一个问题,在咱们须要用到 px2vw 函数的时候,每个中央都要一个个字符手敲也是一件麻烦事,有什么方法能够解决这个问题呢?

这就不得不介绍一下咱们团队开发的 VS Code 插件:Bihu FE Tools。

在 SCSS 值中输出数字值或 px2vw 结尾的值时就会有智能提醒,回车即可一键输出:

还能够对 SCSS 代码选中区域内的 px 值对立加上 px2vw() 调用:

另外还提供了代码片段,能够快捷输出 :global(.mobile) {...}

以上性能都是依据本文所介绍的响应式计划量身定制的。不仅如此,该插件还提供了格式化或将 JSON 转为 TS 类型、组件重命名、带主动导入性能的 useStateuseEffect 代码片段等个性,实用于 JS/TS/React/SCSS 等技术栈,欢送尝试,也欢送提改良倡议。

优缺点

长处

  • 该计划能够很不便地实现代码逻辑复用,款式也大部分能够复用,不统一的中央只须要在 .mobile 选择器中笼罩之前的 CSS 即可,极大进步了开发效率;
  • 我的项目中往往会有些中央不仅须要辨别挪动端和 PC 端的款式,还须要在不同端执行不同的逻辑,这时候用 isMobile 来判断就很不便;
  • 比起单纯应用 CSS 实现的响应式,咱们能够通过 isMobile 值灵便管制组件的渲染,防止渲染多余组件,从而造成不必要的开销;
  • 因为是通过 SCSS 函数对 px 进行转换,既不须要本人手动计算 vw 值,还能够直观地看到原来的 px 大小,不便在编辑器中与设计稿进行比拟。

毛病

  • 实用于简略场景,也就是只须要辨别 H5 和 PC 端的状况,简单场景须要看状况批改,或应用 react-responsive 等库;
  • 因为须要应用 SCSS 函数,挪动端 CSS 编写不太不便,倡议搭配 Bihu FE Tools VS Code 插件)应用。

实用和不实用场景

实用场景

  • 需要上只须要辨别 H5 和 PC 端
  • 代码上 px (PC 端) 和 vw (H5) 共存
  • 实现逻辑和款式的复用,进步开发效率

不实用场景

  • 如果须要对款式进行更细维度的管制,能够用下面提到的 react-responsive 或媒体查问来实现
  • 如果须要将全副或绝大部分 px 转换成 vw,则举荐应用 postcss-px-to-viewport

总结

该项目标残缺代码已上传到 Github: https://github.com/heruns/react-responsive-layout。

本文所介绍的响应式计划就是逻辑上通过 React Hook 来实时获取视口是 PC 还是挪动端大小,而后在尽可能复用代码的状况下在不同端渲染不同的组件、编写不同的逻辑或款式,挪动端应用 SCSS 函数将 px 转成 vw,并且尽量利用构建和开发工具晋升开发体验和效率。

以上就是我集体对公司我的项目响应式计划的思考,欢送大家提出本人的见解,一起探讨、学习。

参考资料

  • React Hooks 响应式布局
  • Developing responsive layouts with React Hooks – LogRocket Blog

正文完
 0