2020-03-04:vue-styled-components

import styled,{ThemeProvider,injectGlobal} from 'vue-styled-components';

// 返回组件选项对象
styled.div`...`

// 可通过props传参的组件
const StyledButton = styled('button', { primary: { 
  type:Boolean,
  default:"some"
})`...` 

// 通过组件创建样式组件
const RouterLink = Vue.component('router-link') 
const StyledLink = styled(RouterLink)`...`

// 主题组件
const Wrapper = styled.section` 
  background: ${props => props.theme.primary};
`; 
<theme-provider :theme="{
  primary: 'palevioletred'
}">
  <wrapper>
    ...
  </wrapper>
</theme-provider>

// 扩展原有样式组件
const TomatoButton = StyledButton.extend`...` 

// 复制样式创建新的样式组件
const Link = Button.withComponent('a') 

// 添加全局样式
injectGlobal`...` 

——————————————————————————————————————————————————————————————————————————————————

基础

https://es6.ruanyifeng.com/#docs/string#模板字符串

  • 注释:模板字符串返回字符串类型
  • 如果${...}中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法
  • 模板字符串能够嵌套
    • 注释:在${...}使用另一个模板字符串

https://es6.ruanyifeng.com/#docs/string#标签模板

  • 模板字符串可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能
alert`hello`
// 等同于
alert(['hello'])
  • 如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数
    • 注释:参数一这个字符串数组是以${}为参考位进行split的,例如'bacada'.split('a') // ["b", "c", "d", ""]'abacad'.split('a') // ["", "b", "c", "d"],即当变量在前后头部时会在前后多出一个空字符串的数组项。也可以理解为常量数组比变量参数长度多1
    • 注释:参数一字符串数组其他还包含一个名为raw的属性stringArr.raw,保存的是转义后的原字符串数组。为了方便取得转义之前的原始模板而设计的
let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

function tag(stringArr, value1, value2){
  // ...
}
// 等同于
function tag(stringArr, ...values){
  // ...
}
  • “标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容
function SaferHTML(templateData) {
  let s = templateData[0];
  for (let i = 1; i < arguments.length; i++) {
    let arg = String(arguments[i]);

    // Escape special characters in the substitution.
    s += arg.replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;");

    // Don't escape special characters in the template.
    s += templateData[i];
  }
  return s;
}

let sender = '<script>alert("abc")</script>'; // 恶意代码
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p>
  • 标签模板的另一个应用,就是多语言转换(国际化处理)
i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "欢迎访问xxx,您是第xxxx位访问者!"
  • 使用标签模板,在 JavaScript 语言之中嵌入其他语言
    • 疑问:vue中的jsx是否采用同样的方式实现?可以帮助理解JSX的属性绑定
    • 疑问:在JS中执行其他语言的代码,实际上是文本处理后转化为相同功能的当前语言后执行?
jsx`
  <div>
    <input
      ref='input'
      onChange='${this.handleChange}'
      defaultValue='${this.state.value}' />
      ${this.state.value}
   </div>
`
  • 模板处理函数的第一个参数(模板字符串数组),还有一个raw属性,保存的是转义后的原字符串数组。为了方便取得转义之前的原始模板而设计的
    • 疑问:JS中各种遍历的本质和区别?
    • 注释:\n解析为文本换行,在字符串和模板字符串中都一样。要定义'\n'字符串应转义为\\n
tag`First line\nSecond line`

function tag(strings) {
  console.log(strings.raw[0]);
  // strings.raw[0] 为 "First line\\nSecond line"
  // 打印输出 "First line\nSecond line"
}

https://es6.ruanyifeng.com/#docs/string#模板字符串的限制

  • 模板字符串默认会将\u当作Unicode 字符、\x当作十六进制字符和\n进行转义,前两个由于格式问题可能导致转义失败,这时包含该字符串的数组项将被填充为undefined,在raw中能拿到转义前字符串
    • 疑问:在其他语言中如果存在${}的语法应该也会导致解析有误
function tag(strs) {
  strs[0] === undefined
  strs.raw[0] === "\\unicode and \\u{55}";
}
tag`\unicode and \u{55}`

————————————————————————————————————————————————————————————————————————————

https://github.com/styled-components/vue-styled-components

  • 使用建立在language-sass和language-css之上的CSS语法

Support

yarn add vue-styled-components

Usage

Basic

  • 注释:styled生成的组件默认含有插槽
  • 注释:styled.div返回的是组件的选项对象
import styled from 'vue-styled-components';
const App = styled.div`
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
`;
const Nav = styled.div`
  padding: 30px;
  a {
    font-weight: bold;
    color: #2c3e50;
    &.router-link-exact-active {
      color: #42b983;
    }
  }
`

export default {
  render() {
    return <App>
      <Nav>
        <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link>
      </Nav>
      <router-view />
    </App>
  }
}

Adapting based on props

  • 注释:可以传入参数控制的styled组件,带有props
  • 注释:这是一个真实的vue props选项,遵循vue的方式
import styled from 'vue-styled-components';

const btnProps = { primary: Boolean };

const StyledButton = styled('button', btnProps)`
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
  background: ${props => props.primary ? 'palevioletred' : 'white'};
  color: ${props => props.primary ? 'white' : 'palevioletred'};
`;

export default StyledButton;

<styled-button primary>Primary</styled-button>

Theming

  • 注释:ThemeProvider只需要在根结点绑定theme对象,采用inject方式向子孙组件传递theme对象,所以普通的对象是不具备响应的
  • 注释:ThemeProvider是一个组件选项对象
  • 疑问:styled.default.div在实际情况中报错,是笔误?
  • 注释:Wrapper中的props.theme并非一个真实的props选项
import styled,{ThemeProvider} from 'vue-styled-components'

const Wrapper = styled.default.section`
  padding: 4em;
  background: ${props => props.theme.primary};
`;

<theme-provider :theme="{
  primary: 'palevioletred'
}">
  <wrapper>
    // ...
  </wrapper>
</theme-provider>

Style component constructors as router-link

  • 注释:styled作为函数时第一个参数不仅可以传入原生标签名字符串还可以传入组件的构造函数来创建样式组件
  • 疑问:传入构造函数应该可以和传入字符串一样使用第二参数创建props供外部传入样式变量
  • 疑问:Vue.component('router-link')不能正确获取组件函数,正确应为Vue.component('RouterLink')
import styled from 'vue-styled-components';
const RouterLink = Vue.component('router-link')
const StyledLink = styled(RouterLink)`
  color: palevioletred;
  font-size: 1em;
  text-decoration: none;
`;

<styled-link to="/">Custom Router Link</styled-link>
  • 使用.extend方法扩展已经定义好了的样式组件,保留组件原有样式并加以扩展或覆盖
import StyledButton from './StyledButton';

const TomatoButton = StyledButton.extend`
  color: tomato;
  border-color: tomato;
`;

export default TomatoButton;

withComponent

  • 注释:可以复制其他组件的样式生成一个新的组件
const Button = styled.button`
  background: green;
  color: white;
`
const Link = Button.withComponent('a')

injectGlobal

  • 全局样式,每个应用最多使用一次
  • 注释:可以在多个组件内使用,使用一次并不是硬性规定
// global-styles.js

import { injectGlobal } from 'vue-styled-components';

injectGlobal`
  @font-face {
    font-family: 'Operator Mono';
    src: url('../fonts/Operator-Mono.ttf');
  }
  body {
    margin: 0;
  }
`;

Syntax highlighting

  • 在vscode中vscode-styled-components插件用来支持语法高亮
  • 注释:目前该插件不支持styled('div',propsObj)...这样的高亮提醒
  • .extend...这个方法也不支持高亮

————————————————————————————————————————————————————————————————————————————

https://github.com/Microsoft/typescript-styled-plugin

  • 注释:用于在ts中实现styled-components高亮提醒,及相关设置
  • 疑问:vscode-styled-components插件在JS中存在问题,是否在TS中表现会更好点?

Usage

With VS Code

  • 疑问:如果使用的是工作区版本的TypeScript,则必须在工作区中的TypeScript版本旁边手动安装插件?

Configuration

Tags

  • 可以通过配置为其他标记名称启用IntelliSense
  • 注释:当需要使用sty来代替styled时需要进行配置
  • 注释:在JS中无法生效,不知道是不是配置文件错误
// tsconfig.json
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "typescript-styled-plugin",
        "tags": [
          "styled",
          "css",
          "sty"
        ]
      }
    ]
  }
}

import sty from 'styled-components'

sty.button`
    color: blue;
`

Linting

  • 疑问:禁用错误报告,TS中对模板语法会出现错误吗?
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "typescript-styled-plugin",
        "validate": false
      }
    ]
  }
}
  • 还可以配置使用linter设置报告错误的方式
{
  "compilerOptions": {
    "plugins": [
      {
        "name": "typescript-styled-plugin",
        "lint": {
          "vendorPrefix": "error",
          "zeroUnits": "ignore"
        }
      }
    ]
  }
}
  • 支持以下选项
    • 注释:略,需要eslint基础

Emmet in completion list

  • 已经默认包括Emmet的输入提醒
  • 疑问:在TS中似乎有问题,需要通过Ctrl+Space显示输入提醒
  • 支持以下选项
    • 注释:略,需要Emmet基础