来聊聊怎么写react-native上的样式吧

更新日期: 2018-02-26阅读: 3k标签: native

我遇到了什么问题?

不久之前我重构了一个古老的项目,总结了一些js方面的想法,不过对于一个前端项目而言不仅仅只由js组成的嘛,上学的时候老师和我说html+css+JS对应的是页面的骨架、皮肤和肌肉。既然骨架我们有了,肌肉也聊完了,今天我们就来聊聊“皮肤”吧。

由于我重构的是一个react-native项目,所以我们先来说说在react-native上是怎么写样式的吧,和传统的web不一样的是,在react-native上面是没有css代码,不过得益于Yoga,我们可以在客户端上像写css一样的去书写我们的样式。我们来看看react-native文档上是怎么说的吧:

在React Native中,你并不需要学习什么特殊的语法来定义样式。我们仍然是使用JavaScript来写样式。所有的核心组件都接受名为style的属性。这些样式名基本上是遵循了web上的CSS的命名,只是按照JS的语法要求使用了驼峰命名法,例如将background-color改为backgroundColor。

style属性可以是一个普通的JavaScript对象。这是最简单的用法,因而在示例代码中很常见。你还可以传入一个数组——在数组中位置居后的样式对象比居前的优先级更高,这样你可以间接实现样式的继承。

没错,你几乎不需要什么成本就可以按照写css一样的写法去写我们的rn样式,我们来看一下文档中的例子:

import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View } from 'react-native';

export default class LotsOfStyles extends Component {
  render() {
    return (
      <View>
        <Text style={styles.red}>just red</Text>
        <Text style={{
            color: 'blue',
            fontWeight: 'bold',
            fontSize: 30,
        }}>just bigblue</Text>
        <Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>
        <Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  bigblue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});

AppRegistry.registerComponent('LotsOfStyles', () => LotsOfStyles);

在上面的demo中,我们有两种方式去写我们的样式,它和我们在写css时候遇到的外联式样式、内联式样式很相似,而项目中我们总是习惯将样式和页面分离,然后把他们都放在另外一个style.js文件中。这是个非常不错的习惯,但是也造成了一些困扰。

面对一个页面,我该怎么去模块化它的样式呢?在之前的项目中虽然做到了样式和页面的分离,让页面“看起来”干净了很多,但是在 style.js 文件中仍然是杂乱的代码,大量重复的变量、重复的内容、重复的声明。。。这个时候又有同学说了,我们可以把这些公共的变量、代码分离出来放到一个主题文件中呀,于是项目中除了各个页面的style.js之外又在全局出现了一个theme.js文件,在这里大家愉快的把诸如颜色、大小、布局等公共的代码放了进来。

这看似解决了style.js里重复冗余的代码,但是也会让我的import变得混乱:

import { StyleSheet } from 'react-native';
import {
  px,
  COLOR_BG_RED,
  COLOR_BG_GREEN,
  STYLE_FR_VC_HSB,
  STYLE_FR_VC_HC,
  STYLE_FR_VC_HFS,
} from 'MyStyle';

export default StyleSheet.create({
    // TODO
});

看到这里,我想你除了知道我import进来了两个颜色之外,对于其它变量会一头雾水吧,除非你去MyStyle模块里面亲眼看一下才会知道真正引入进来的是些什么了,如果这里的样式特别的多的话,除了再新建一个sytle.js之外,你就只能每次回到头部去看看自己引入了些什么。这是我不能忍受的。。。


为你的样式分类

除了由于一次性引入太多的公共样式导致我要来回滑动之外,当我再去写一个新的styel.js文件时,复制这么多引入也是一件头疼的事情,那么我能不能每次只需要写一行import呢?如果我的样式都是按固定规则分类放好的是不是每次就可以只import这几个类了呢?

经常写css的同学一定注意过样式的书写顺序,某一类的属性写在一起,虽然在web中,这样写是为了优化css引擎,但是这也体现出了样式是有一定类型的,控制颜色的、控制边距的、控制布局的,那么我们的公共变量是不是也可以按照这样的规则来声明呢?

import { color, size, layout } from 'MyStyle';

这样我们文件的头部是不是就清晰多了呢?在写代码的时候,也不需要再关心我之前引入了些什么了,只要只要关注我们要写什么就行了:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      borderLeftWidth: size.border,
      borderRightWidth: size.border,
      borderBottomWidth: size.border,
      borderColor: color.border,
      // 子元素横向排列,垂直居中,水平分布,中间用空格填满,最两边元素各自靠边
      ...layout.flex.vchbs,
    },
});

在我的项目中默认边框的大小就是一个像素(1px),那么只要在最外层声明了 size.border的大小,后面写代码的时候就可以畅行无阻的书写下去了,其实我们已经模块化了,只是我们还不够彻底,不彻底就代表着我们的代码不完美,而且可复用性差,就如上面的demo,如果我们这里需要一个三面的边框,那么其它组件需不需要呢?如果需要的话是不是也可以像我这样写呢?

当然是不可以!为什么?因为我们是在复用这个边框,所以我们就不该再写一份一模一样的代码了,而是应该写类似这样的:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      // 一个边框粗细为1px的红色边框
      ...layout.border
      // 子元素横向排列,垂直居中,水平分布,中间用空格填满,最两边元素各自靠边
      ...layout.flex.vchbs,
    },
});

这样我们的代码不仅少了很多,结构也清楚了,而且到时候替换或者修改的时候也容易一些了,不过写成这样就结束了嘛?当然不是了,我们现在有一个红色的边框,所以我们在layout模块下新增了一个border属性,那么如果我们有一个蓝色的边框呢?一个绿色的粗边框呢?我们会一直往layout模块上新增属性嘛?那最后你知道layout上面究竟有多少属性嘛?那不就又回到一开始了嘛。。。

所以,我的建议是,处于根节点的模块最好控制在3个左右:

  • color:用于存放整个项目的全部颜色,这也代表着,在组件的style内部,我们不应该再显示的书写诸如backgroundColor: '#fff'这样的代码了。
  • size:用于存放整个项目的通用大小,比如说行高、间距、字体大小等公共的数值参数。
  • layout:用于存放整个项目的公共布局,例如控制布局的flex属性、通用的padding、margin、position定位。

那么第二级中的属性我也建议控制在5个左右:

  • 颜色:边框颜色、背景颜色、字体颜色。。。
  • 大小:边框大小、间距大小、字体大小。。。
  • 布局:flex布局、position定位。。。

这样虽然增加了深度,但是分类清晰,结构明确,复用性也比较高。虽然可能会增加项目新建时的成本(创建各种分类),但是会给后续的开发、迁移、重构、复用等带来极大的便捷。但这就结束了嘛?有的同学和我说,我有很多的边框啊,我有很多样式要复用啊,到最后我的layout也会大到看不懂啊。。。还有的同学说我没有那么多可复用的样式啊,那是不是你总结的思路就用不上了啊。当然不是咯,我们只完成了样式模块化的第一步(抽离样式),接下来开始第二步。


该怎么更便捷的写样式?

现在很多web开发者在书写css的时候已经不再去写原生的css了吧,而是采用例如scss、less这样的预编译语言去写样式了,那么这些预编译语言给我们带来了哪些方便呢?我想大多数同学第一时间都会想到Mixin

利用混合器,可以很容易地在样式表的不同地方共享样式。如果你发现自己在不停地重复一段样式,那就应该把这段样式构造成优良的混合器,尤其是这段样式本身就是一个逻辑单元,比如说是一组放在一起有意义的属性。

在react-native上面,我们的样式代码是js代码,所以很天然的就自带预编译,不需要其它额外的语言去处理它,要做的只是判断你的属性是否需要一个Mixin。

判断一组属性是否应该组合成一个混合器,一条经验法则就是你能否为这个混合器想出一个好的名字。如果你能找到一个很好的短名字来描述这些属性修饰的样式,比如rounded-cornersfancy-font或者no-bullets,那么往往能够构造一个合适的混合器。如果你找不到,这时候构造一个混合器可能并不合适。

那么在js上面,我该如何实现一个Mixin呢?太简单了!我们只需要一个函数就可以了,没错,只需要一个返回对象的函数就可以做到这样的效果了,加上ES7的拓展运算符,我们就可以做到一个混合器的效果:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      ...layout.border(1px, '#fff')
    },
});

常写react-native的同学一定都头疼过这样一个问题吧,就是我们并不能像写css样式一样在一行中把所有的属性都写完,在css中我们如果想要声明一个四面边框的大小,可以这样写:

.border {
    border: 10px 5px 10px 5px;
}

那么在我们写样式的时候是不是也可以这样写:

export default StyleSheet.create({
    lines: {
      height: px(88),
      backgroundColor: color.background,
      ...layout.border(10px, 5px, 10px, 5px),
    },
});

我们可以通过函数的不同数量的参数来模拟传统css开发的简写属性,很多时候我们更习惯在View上面去做样式的运算,利用react-native样式的覆盖数组去不断的覆盖之前的样式来达到运算的结果,这就导致View中除了需要计算你的组件要不要展示、如何展示之外,还要去计算样式该如何写,既然我们要做样式和页面的分离,那就应该做彻底一些,将样式的计算也放在style.js中。


总结

最后总结一下我们所做的:

  • 分离样式和页面
  • 提取项目级的公共属性
  • 归类提取的公共样式
  • 通过混合器去创造模板样式

我建议无论你的项目多大,代码多少,前三步都应该是一个必备的环节,可能你的项目不复杂,暂时用不到第四点,但前三条无论如何都应该尽早的去完善,这不仅仅能帮助你实现后续的迭代,也能在你的脑中保留出一个对于项目完整结构的印象,要知道样式是寄生于页面的,清楚了样式,那么页面如何你也多少会烂熟于心了。而相比于通过梳理js的逻辑去了解整个项目,我想通过页面也许会更快吧,这对刚刚接手项目的新同学来说,是非常友善的。


最后的最后

一般到这里,就该放上自己开源的项目地址或者安利一波作者写的库了,不过和上一篇一样,这里我们只讨论思路,表述想法,而具体的实践和代码还是要靠我们自己在项目中不断的总结和积累~

我相信很多同学对于我提到的前三点都会很快的理解,而对于第四点可能就有些懵了,该怎么去理解这个混合器呢?我该怎么用js去实现一个呢?下面我就用一段代码来举个例子,该如何实现一个Mixin:

const layout = {
  // 这里的形参顺序遵循css中的 “上、右、下、左”
  margin(...arg) {
    let margin = {};
    switch (arg.length) {
      case 1:
        margin = {
          marginTop: arg[0],
          marginRight: arg[0],
          marginBottom: arg[0],
          marginLeft: arg[0],
        };
        break;
      case 2:
        margin = {
          marginVertical: arg[0],
          marginHorizontal: arg[1],
        };
        break;
      case 3:
        margin = {
          marginTop: arg[0],
          marginHorizontal: arg[1],
          marginBottom: arg[2],
        };
        break;
      case 4:
        margin = {
          marginTop: arg[0],
          marginRight: arg[1],
          marginBottom: arg[2],
          marginLeft: arg[3],
        };
        break;
      default:
        break;
    }
    return margin;
  },
};

这是一个最简易的Mixin,你可以根据你的需求去写更多这样的Mixin,其实我个人觉得在项目一开始的时候是不一定需要这个的,这个存在的意义是对于复杂样式书写的,更多的情况下,你的项目只要做到了前三点,在样式这一块就已经非常的整洁、完善了,多数情况下你不需要Mixin就能组织好你的代码。


原文地址:https://www.cnblogs.com/fuhuixiang/p/8461200.html  

链接: https://www.fly63.com/article/detial/443

WKWebView 里 JS 和 native 通信的例子

初始化 wkwebview,设置 message handler,native 端注册了 testecho 的messageHandler,实现 WKScriptMessageHandler协议,执行JS 代码 ,所以 JS 可以通过 window.webkit.messageHandlers.testecho.postMessage 来回调客户端,和文档中说的一样

由使用request-promise-native想到的异步处理方法

因为js语言的特性,使用node开发程序的时候经常会遇到异步处理的问题。对于之前专长App开发的我来说,会纠结node中实现客户端API请求的“最佳实践”。下面以OAuth2.0为场景,需要处理的流程:获取access token、使用获取到的token,发起API请求、处理API数据

React Native项目使用react-apollo实现更新缓存的两种方式

GraphQL是一个API查询语言,他可以将使用PostgreSQL写的server代码自动生成Query或者Mutation,非常的方便。而Apollo Client就是一个强大的JavaScript GraphQL客户端。对于cache,在Apollo Client中有着强大的管理策略。

11个React Native组件库

React Native 是 Facebook 2015年开源的 Javascript 框架,旨在使用 Javascript 高效开发手机端 App。根据大众的需求,我们列出了一个有用的React-Native UI库列表,可以帮助你更好地入门React Native。

React Native 传参的几种方式

在React Native 中由于业务的需要, 我们往往要在诸多的页面间,组件之间做一些参数的传递与管理, 在这里我总结了几大经过验证,稳定好用的方式给大家

如何正确选型,React Native 还是 Native?

随着 H5 标准的发布以及推广,使得移动应用的开发也受到了很大影响,出于效率、成本等原因,移动应用的开发不再完全依赖于 “原生”。近日越发火热的混合应用(Hybrid App)介于 Web 应用和原生应用之间

react-native报错Cannot get property packageName on null object

react-native打包安卓apk的时候,报错Cannot get property packageName on null object,完全没有头绪,研究了半天才发现竟然是因为package.json里面scripts自定了命令导致的,无法理解为何会影响安卓打包并且报错packageName null

React Native 添加 Redux 支持

之前写的项目都是人家编写好的脚手架,里面包含项目所需的环境文件,但由于有些东西用不到打包增加软件体积,所以自己从头搭建个环境。是基于 Native Base + react-navigation + Redux 的 React Native 脚手架,现在项目环境如下

Flutter platform view 使用篇

Flutter作为备受关注的跨平台的开发框架,长远来看,前景肯定是比较好的,在其基础组件还未完善与成熟之前,能够高效的复用现有的native组件,是比较合适的方案。官方提供了Plugin的方式,允许将一个成熟的native组件(比如mapview)

JSBridge 初探

近些年,移动端普及化越来越高,开发过程中选用 Native 还是 H5 一直是热门话题。Native 和 H5 都有着各自的优缺点,为了满足业务的需要,公司实际项目的开发过程中往往会融合两者进行 Hybrid 开发。Native 和 H5 分处两地,看起来无法联系,那么如何才能让双方协同实现功能呢?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!