如何用React Hooks和Context API替换Redux

如何用React Hooks和Context API替换Redux

时间:2020-9-27 作者:admin
在React中处理共享应用程序状态的最流行方法是使用诸如Redux之类的框架。最近,React团队引入了几个新功能,其中包括React Hooks和Context API。这两个功能有效地消除了大型React项目的开发人员面临的许多挑战。最大的问题之一是“ prop钻”,这是嵌套组件常见的问题。解决方案是使用像Redux这样的状态管理库。不幸的是,这付出了编写样板代码的代价。但是现在可以用React Hooks和Context API替换Redux。在本教程中,您将学习一种在React项目中处理状态的新方法,而无需编写过多的代码或安装大量的库-Redux就是这种情况。React挂钩允许您使用函数组件内部的本地状态,而Context API允许您与其他组件共享状态。

先决条件

为了继续学习本教程,您需要熟悉以下主题:

  • 反应
  • 反应钩
  • Redux

您将在此处学习的技术基于Redux中引入的模式。这意味着您需要对进行深入了解,reducers然后actions再继续。我当前正在使用Visual Studio Code,它似乎是目前最受欢迎的代码编辑器(尤其是对于JavaScript开发人员而言)。如果您使用的是Windows,我建议您安装Git Bash。使用Git Bash终端执行本教程中提供的所有命令。Cmder还是一个很好的终端,能够在Windows上执行大多数Linux命令。

您可以在GitHub Repository上访问本教程中使用的完整项目。

关于新的状态管理技术

我们需要在React项目中处理两种状态:

  • 地方政府
  • 全球状态

局部状态只能在定义它们的组件内使用。全局状态可以在多个组件之间共享。以前,定义全局状态需要安装状态管理框架,例如Redux或MobX。随着React v16.3.0的发布,Context API得以发布,它允许开发人员无需安装其他库即可实现全局状态。

从React v16.8开始,Hooks允许在组件中实现许多React功能,而无需编写类。Hooks为React开发人员编写代码的方式带来了巨大的好处。这包括代码重用和在组件之间共享状态的更简便方法。对于本教程,我们将关注以下React钩子:

  • useState
  • useReducer

useState建议用于处理数字或字符串之类的简单值。但是,在处理复杂的数据结构时,您将需要使用useReducer钩子。对于useState,您只需要一个setValue()函数即可覆盖现有状态值。

对于useReducer,您将要处理一个状态对象,该状态对象以树状结构包含多个具有不同数据类型的值。您需要声明可以更改一个或多个这些状态值的函数。对于数组等数据类型,您需要声明多个不可变的函数来处理添加,更新和删除操作。在本教程的后面部分中,您将看到一个示例。

使用useState或声明状态后useReducer,您需要使用React Context将其提升为全局状态。这是通过使用React库提供的功能创建一个上下文对象来完成的createContext。上下文对象允许状态在组件之间共享,而无需使用道具。

您还需要为上下文对象声明一个上下文提供程序。这允许页面或容器组件订阅上下文对象以进行更改。容器的任何子组件都可以使用该useContext函数访问上下文对象。

现在,让我们看看实际的代码。

设置项目

我们将使用create-react-app快速启动我们的项目:

$ npx create-react-app react-hooks-context-app

接下来,让我们安装Semantic UI React,这是一个基于React的CSS框架。这不是必需的;我只是喜欢创建漂亮的用户界面而无需编写自定义CSS:

yarn add semantic-ui-react fomantic-ui-css

打开src/index.js并插入以下导入:

import 'fomantic-ui-css/semantic.min.css';

这就是我们要开始使用语义UI的项目所需要做的。在下一节中,我们将研究如何使用useState钩子声明状态并将其提升为全局状态。

反例:useState

在此示例中,我们将构建一个简单的计数器演示,该演示由两个按钮组件和一个显示组件组成。我们将介绍一种count状态,该状态将在两个组件之间全局共享。组件将是的子代CounterView,它将充当容器。按钮组件将具有将增加或减少count状态值的按钮。

让我们首先count在名为的上下文文件中定义状态context/counter-context.js。在src文件夹中创建此文件并插入以下代码:

import React, { useState, createContext } from "react";

// Create Context Object
export const CounterContext = createContext();

// Create a provider for components to consume and subscribe to changes
export const CounterContextProvider = props => {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={[count, setCount]}>
      {props.children}
    </CounterContext.Provider>
  );
};

我们定义了一个名为的状态count,并将默认值设置为0。所有使用的组件CounterContext.Provider都可以访问count状态和setCount功能。让我们在中定义用于显示count状态的组件src/components/counter-display.js

import React, { useContext } from "react";
import { Statistic } from "semantic-ui-react";
import { CounterContext } from "../context/counter-context";

export default function CounterDisplay() {
  const [count] = useContext(CounterContext);

  return (
    <Statistic>
      <Statistic.Value>{count}</Statistic.Value>
      <Statistic.Label>Counter</Statistic.Label>
    </Statistic>
  );
}

接下来,让我们定义组件,该组件将包含用于增加和减少state组件的按钮。创建文件src/components/counter-buttons.js并插入以下代码:

import React, { useContext } from "react";
import { Button } from "semantic-ui-react";
import { CounterContext } from "../context/counter-context";

export default function CounterButtons() {
  const [count, setCount] = useContext(CounterContext);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div>
      <Button.Group>
        <Button color="green" onClick={increment}>
          Add
        </Button>
        <Button color="red" onClick={decrement}>
          Minus
        </Button>
      </Button.Group>
    </div>
  );
}

实际上,useContext由于我们尚未指定Provider,因此该功能将无法使用。现在,通过在中创建一个容器来做到这一点src/views/counter-view.js。插入以下代码:

import React from "react";
import { Segment } from "semantic-ui-react";

import { CounterContextProvider } from "../context/counter-context";
import CounterDisplay from "../components/counter-display";
import CounterButtons from "../components/counter-buttons";

export default function CounterView() {
  return (
    <CounterContextProvider>
      <h3>Counter</h3>
      <Segment textAlign="center">
        <CounterDisplay />
        <CounterButtons />
      </Segment>
    </CounterContextProvider>
  );
}

最后,让我们App.js用以下代码替换现有代码:

import React from "react";
import { Container } from "semantic-ui-react";

import CounterView from "./views/counter-view";

export default function App() {
  return (
    <Container>
      <h1>React Hooks Context Demo</h1>
      <CounterView />
    </Container>
  );
}

现在,您可以create-react-app使用以下yarn start命令启动服务器。浏览器应启动并呈现您的计数器。单击按钮以确保incrementdecrement功能正常运行。

您也可以在CodePen上测试此代码。

让我们进入下一部分,在这里我们将建立一个使用useReducer钩子的高级示例。

联系人示例:useReducer

在此示例中,我们将构建一个用于管理联系人的基本CRUD页面。它由几个演示组件和一个容器组成。还有一个用于管理联系人状态的上下文对象。由于我们的状态树比前面的示例要复杂一些,因此我们必须使用useReducer钩子。

创建状态上下文对象src/context/contact-context.js并插入以下代码:

import React, { useReducer, createContext } from "react";

export const ContactContext = createContext();

const initialState = {
  contacts: [
    {
      id: "098",
      name: "Diana Prince",
      email: "diana@us.army.mil"
    },
    {
      id: "099",
      name: "Bruce Wayne",
      email: "bruce@batmail.com"
    },
    {
      id: "100",
      name: "Clark Kent",
      email: "clark@metropolitan.com"
    }
  ],
  loading: false,
  error: null
};

const reducer = (state, action) => {
  switch (action.type) {
    case "ADD_CONTACT":
      return {
        contacts: [...state.contacts, action.payload]
      };
    case "DEL_CONTACT":
      return {
        contacts: state.contacts.filter(
          contact => contact.id !== action.payload
        )
      };
    case "START":
      return {
        loading: true
      };
    case "COMPLETE":
      return {
        loading: false
      };
    default:
      throw new Error();
  }
};

export const ContactContextProvider = props => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <ContactContext.Provider value={[state, dispatch]}>
      {props.children}
    </ContactContext.Provider>
  );
};

创建父组件src/views/contact-view.js并插入以下代码:

import React from "react";
import { Segment, Header } from "semantic-ui-react";
import ContactForm from "../components/contact-form";
import ContactTable from "../components/contact-table";
import { ContactContextProvider } from "../context/contact-context";

export default function Contacts() {
  return (
    <ContactContextProvider>
      <Segment basic>
        <Header as="h3">Contacts</Header>
        <ContactForm />
        <ContactTable />
      </Segment>
    </ContactContextProvider>
  );
}

创建演示文稿组件src/components/contact-table.js并插入以下代码:

import React, { useState, useContext } from "react";
import { Segment, Table, Button, Icon } from "semantic-ui-react";
import { ContactContext } from "../context/contact-context";

export default function ContactTable() {
  // Subscribe to `contacts` state and access dispatch function
  const [state, dispatch] = useContext(ContactContext);
  // Declare a local state to be used internally by this component
  const [selectedId, setSelectedId] = useState();

  const delContact = id => {
    dispatch({
      type: "DEL_CONTACT",
      payload: id
    });
  };

  const onRemoveUser = () => {
    delContact(selectedId);
    setSelectedId(null); // Clear selection
  };

  const rows = state.contacts.map(contact => (
    <Table.Row
      key={contact.id}
      onClick={() => setSelectedId(contact.id)}
      active={contact.id === selectedId}
    >
      <Table.Cell>{contact.id}</Table.Cell>
      <Table.Cell>{contact.name}</Table.Cell>
      <Table.Cell>{contact.email}</Table.Cell>
    </Table.Row>
  ));

  return (
    <Segment>
      <Table celled striped selectable>
        <Table.Header>
          <Table.Row>
            <Table.HeaderCell>Id</Table.HeaderCell>
            <Table.HeaderCell>Name</Table.HeaderCell>
            <Table.HeaderCell>Email</Table.HeaderCell>
          </Table.Row>
        </Table.Header>
        <Table.Body>{rows}</Table.Body>
        <Table.Footer fullWidth>
          <Table.Row>
            <Table.HeaderCell />
            <Table.HeaderCell colSpan="4">
              <Button
                floated="right"
                icon
                labelPosition="left"
                color="red"
                size="small"
                disabled={!selectedId}
                onClick={onRemoveUser}
              >
                <Icon name="trash" /> Remove User
              </Button>
            </Table.HeaderCell>
          </Table.Row>
        </Table.Footer>
      </Table>
    </Segment>
  );
}

创建演示文稿组件src/components/contact-form.js并插入以下代码:

import React, { useState, useContext } from "react";
import { Segment, Form, Input, Button } from "semantic-ui-react";
import _ from "lodash";
import { ContactContext } from "../context/contact-context";

export default function ContactForm() {
  const name = useFormInput("");
  const email = useFormInput("");
  // eslint-disable-next-line no-unused-vars
  const [state, dispatch] = useContext(ContactContext);

  const onSubmit = () => {
    dispatch({
      type: "ADD_CONTACT",
      payload: { id: _.uniqueId(10), name: name.value, email: email.value }
    });
    // Reset Form
    name.onReset();
    email.onReset();
  };

  return (
    <Segment basic>
      <Form onSubmit={onSubmit}>
        <Form.Group widths="3">
          <Form.Field width={6}>
            <Input placeholder="Enter Name" {...name} required />
          </Form.Field>
          <Form.Field width={6}>
            <Input placeholder="Enter Email" {...email} type="email" required />
          </Form.Field>
          <Form.Field width={4}>
            <Button fluid primary>
              New Contact
            </Button>
          </Form.Field>
        </Form.Group>
      </Form>
    </Segment>
  );
}

function useFormInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  const handleChange = e => {
    setValue(e.target.value);
  };

  const handleReset = () => {
    setValue("");
  };

  return {
    value,
    onChange: handleChange,
    onReset: handleReset
  };
}

相应地插入以下代码App.js

import React from "react";
import { Container } from "semantic-ui-react";
import ContactView from "./views/contact-view";

export default function App() {
  return (
    <Container>
      <h1>React Hooks Context Demo</h1>
    <ContactView />
    </Container>
  );
}

实施代码后,您的浏览器页面应刷新。要删除联系人,您需要先选择一行,然后点击“ 删除”按钮。要创建新联系人,只需填写表格并点击“ 联系人”按钮。

您也可以在CodePen上测试此代码。

查看代码以确保您理解所有内容。阅读我包含在代码中的注释。

概要

我希望这些示例在您了解如何无需Redux的情况下如何在React应用程序中管理共享应用程序状态的过程中为您提供帮助。如果您不使用钩子和上下文API来重写这些示例,那将导致更多的代码。看到不处理道具而编写代码有多容易?

在第二个示例中,您可能已经注意到有两个未使用的状态变量- loadingerror。作为挑战,您可以进一步改进此应用程序以利用它们。例如,您可以实施虚假延迟,并使演示文稿组件显示加载状态。您还可以更进一步,并访问真正的远程API。在此error状态变量可用于显示错误消息。

您现在可能要问自己一个唯一的问题:Redux对将来的项目是否必要?我用这种技术看到的一个缺点是您不能使用Redux DevTool扩展来调试应用程序状态。但是,随着新工具的开发,这种情况将来可能会改变。显然,作为开发人员,您仍然需要学习Redux才能维护旧项目。但是,如果您要开始一个新项目,则需要询问您自己和您的团队,是否真的有必要使用第三方状态管理库。

如何用React Hooks和Context API替换Redux

迈克尔·万约克

我编写干净,可读和模块化的代码。我喜欢学习可以提高效率和工作效率的新技术。

现在有新书了!

如何用React Hooks和Context API替换Redux

了解Git的工作原理,以及如何使用它来简化您的工作流程!

立即阅读这本书

如何用React Hooks和Context API替换Redux

Google,Netflix和ILM是Python用户。也许你也应该呢?

立即阅读这本书
React的受欢迎程度没有丝毫减弱的迹象,在全球许多城市,对开发人员的需求仍超过供应。对于经验不足的开发人员(或那些已经失业了一段时间的开发人员),在面试阶段展示您的知识可能会令人生畏。在本文中,我们将探讨十五个问题,这些问题涵盖了对于理解和有效使用React至关重要的一系列知识。对于每个问题,我将总结答案并提供指向其他资源的链接,您可以在其中找到更多信息。

1.什么是虚拟DOM?

回答

虚拟DOM是组成应用程序UI的实际HTML元素的内存表示形式。重新渲染组件时,虚拟DOM会将更改与其DOM模型进行比较,以创建要应用的更新列表。主要优点是它高效,仅对实际DOM进行最小的必要更改,而不必重新渲染大块。

进一步阅读

  • 了解虚拟DOM
  • 虚拟DOM解释

2.什么是JSX?

回答

JSX是JavaScript语法的扩展,它允许编写类似于HTML的代码。它可以编译为常规的JavaScript函数调用,从而为创建组件标记提供了一种更好的方法。

拿这个JSX:

<div className="sidebar" />

它将转换为以下JavaScript:

React.createElement(
  'div',
  {className: 'sidebar'}
)

进一步阅读

  • JSX简介
  • 深度学习JSX

3.类组件和功能组件之间有什么区别?

回答

在React 16.8(引入钩子)之前,使用基于类的组件来创建需要维护内部状态或利用生命周期方法(即componentDidMountshouldComponentUpdate)的组件。基于类的组件是ES6类,它扩展了React的Component类,并至少实现了一个render()方法。

类组件

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

功能组件是无状态的(同样,<React 16.8),并返回要渲染的输出。它们是渲染仅依赖道具的UI的首选,因为它们比基于类的组件更简单,性能更高。

功能组成

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

注意:在React 16.8中引入钩子意味着这些区别不再适用(请参阅问题14和15)。

进一步阅读

  • React中的功能组件与类组件
  • React中的功能与类组件

4.钥匙有什么用?

回答

在React中渲染集合时,向每个重复的元素添加关键字对于帮助React跟踪元素与数据之间的关联非常重要。密钥应该是唯一ID,最好是UUID或收集项中的其他唯一字符串:

<ul>
  {todos.map((todo) =>
    <li key={todo.id}>
      {todo.text}
    </li>
  )};
</ul>

在集合中添加和删除项目时,不使用键或将索引用作键会导致奇怪的行为。

进一步阅读

  • 列表和键
  • 了解React的关键道具

5.状态和道具有什么区别?

回答

道具是从其父级传递到组件的数据。它们不应被突变,而应仅显示或用于计算其他值。状态是组件的内部数据,可以在组件的生存期内进行修改,并在重新渲染之间进行维护。

进一步阅读

  • 道具与状态

6.为什么调用setState而不是直接改变状态?

回答

如果您尝试直接改变组件的状态,React将无法得知它需要重新渲染组件。通过使用该setState()方法,React可以更新组件的UI。

奖金

另外,您还可以谈谈如何不保证状态更新是同步的。如果您需要根据另一种状态(或道具)更新组件的状态,请将一个函数传递给setState()take stateprops作为其两个参数:

this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

进一步阅读

  • 正确使用状态

7.如何限制作为道具传递的值的类型或使其成为必需值?

回答

为了对组件的props进行类型检查,您可以使用该prop-types包(以前在15.5之前作为React的一部分提供)来声明期望值的类型以及是否需要prop:

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

进一步阅读

  • 使用原型进行类型检查

8.什么是支撑钻,如何避免?

回答

当您需要将数据从父组件向下传递到层次结构中较低的组件,“钻取”其他不需要传递道具的组件时,就会发生道具钻探。

有时,可以通过重构组件,避免过早将组件分解为较小的组件以及将公共状态保持在最接近的公共父级中来避免进行钻探。如果您需要在组件树中深/远的组件之间共享状态,则可以使用React的Context API或专用的状态管理库(例如Redux)。

进一步阅读

  • 道具钻

9.什么是React上下文?

回答

React提供了上下文API,以解决应用内多个组件之间共享状态的问题。在引入上下文之前,唯一的选择是引入一个单独的状态管理库,例如Redux。但是,许多开发人员认为Redux引入了许多不必要的复杂性,尤其是对于较小的应用程序。

进一步阅读

  • 上下文(反应文档)
  • 如何用React Hooks和Context API替换Redux

10.什么是Redux?

回答

Redux是用于React的第三方状态管理库,是在上下文API存在之前创建的。它基于状态容器(称为商店)的概念,组件可以将其作为道具接收数据。更新商店的唯一方法是向商店分派一个动作,该动作将传递到化简器中。精简器接收操作和当前状态,并返回新状态,从而触发订阅的组件重新呈现。

如何用React Hooks和Context API替换Redux

进一步阅读

  • Redux入门
  • 深入研究Redux

11.样式化React应用程序的最常见方法是什么?

回答

有多种方法可用于对React组件进行样式设置,每种方法各有利弊。要提到的主要是:

  • 内联样式:非常适合原型制作,但是有局限性(例如,没有伪类的样式)
  • 基于类的CSS样式:比内联样式更高效,并且对React刚接触的开发人员熟悉
  • CSS-in-JS样式:有许多库允许将样式在组件内声明为JavaScript,从而将样式更像代码。

进一步阅读

  • 如何设置React组件的样式

12.受控组件和非受控组件有什么区别?

回答

在一个HTML文档中,许多形式的元素(例如<select><textarea><input>)保持自己的状态。不受控制的组件将DOM视为这些输入状态的真实来源。在受控组件中,内部状态用于跟踪元素值。当输入值更改时,React重新渲染输入。

与非反应代码集成时,不受控制的组件可能很有用(例如,如果您需要支持某种jQuery表单插件)。

进一步阅读

  • 受控输入与非受控输入
  • 受控组件(React Docs)
  • 不受控制的组件(React Docs)

13.生命周期方法是什么?

回答

基于类的组件可以声明在其生命周期中某些特定时刻调用的特殊方法,例如何时挂载(渲染到DOM中)以及何时要挂载它。例如,它们对于设置和拆除组件可能需要的东西,设置计时器或绑定到浏览器事件很有用。

可以在组件中实现以下生命周期方法:

  • componentWillMount:在创建组件之后但在将其呈现到DOM中之前调用
  • componentDidMount:在第一个渲染之后调用;组件的DOM元素现在可用
  • componentWillReceiveProps:道具更新时调用
  • shouldComponentUpdate:当接收到新的道具时,此方法可以防止重新渲染以优化性能
  • componentWillUpdate:在收到新道具 shouldComponentUpdate返回时调用true
  • componentDidUpdate:在组件更新后调用
  • componentWillUnmount:在从DOM中删除组件之前调用,允许您清理事件监听器之类的东西。

处理功能组件时,该useEffect挂钩可用于复制生命周期行为。

进一步阅读

  • 反应生命周期方法图
  • 组件生命周期API

14.什么是React钩子?

回答

钩子是React试图将基于类的组件(即内部状态和生命周期方法)的优点引入功能组件的尝试。

进一步阅读

  • 在5分钟内学习React Hooks
  • React Hooks:如何入门和构建自己的

15. React钩子有什么优点?

回答

向React引入钩子有几个明显的好处:

  • 不再需要基于类的组件,生命周期挂钩和this关键字恶作剧
  • 通过将通用功能抽象到自定义钩子中,使重用逻辑更加容易
  • 通过能够从组件本身中分离出逻辑,使代码更具可读性,可测试性

进一步阅读

  • React挂钩的好处
  • React Hooks —优点和与较早的可重用逻辑方法的比较

包起来

尽管决不是一个详尽的清单(反应在不断发展),但这些问题涉及很多领域。了解这些主题将使您对图书馆有很好的工作知识,以及图书馆的一些最新变化。跟进建议的进一步阅读将有助于您巩固理解,因此您可以展示出深入的知识。

我们将跟进有关React采访代码练习的指南,因此请在不久的将来留意这一点。

祝好运!

如何用React Hooks和Context API替换Redux

尼尔森·雅克

尼尔森(Nilson)是一名全职的Web开发人员,从事计算机和Web工作已有十多年了。曾任硬件技术员和网络管理员。Nilson现在是一家为建筑行业开发Web应用程序的公司的联合创始人和开发商。您也可以在SitePoint论坛上找到Nilson作为指导者。

学习date-fns:轻量级JavaScript日期库

由  詹姆斯·希巴德

的JavaScript

分享:

 

如何用React Hooks和Context API替换Redux在JavaScript中使用日期是一种痛苦。本地日期方法通常很冗长,有时也不一致 —这也使它们易于出错。但是,好消息即将到来。有几个库可以消除过时的痛苦。这些库是JavaScript日期,而jQuery是本机DOM API。

让我举一个例子。这是对堆栈溢出问题的公认答案,该问题询问如何获取每月的最后一天:

var t = new Date();
alert( new Date(t.getFullYear(), t.getMonth() + 1, 0, 23, 59, 59) );

当然可以,但是后面的数字getMonth代表什么还不是很明显。现在,与更具可读性的对比一下:

const today = new Date();
console.log( lastDayOfMonth(today) );

lastDayOfMonth方法是date-fns提供的,date-fns是一个自称的综合工具集,用于在浏览器和Node.js中操纵JavaScript日期。

在本文中,我将向您展示如何启动和运行date-fns。阅读后,您将可以将其放入项目中,并利用其许多辅助方法轻松地操作日期。这将使代码t.getMonth() + 1, 0, 23, 59, 59成为过去。

那么,为什么不仅仅使用Moment.js?

Moment.js是一个出色的库,用于在JavaScript中处理日期-它具有许多出色的功能,并提供了大量有用的实用程序。但是,并非没有批评者。

许多人都引用Moment对象是可变的(例如add,诸如或subtract更改原始Moment对象的操作)的事实,这使开发人员感到困惑,并且导致了bug。

它的大尺寸也受到了抨击。Moment在现代的“摇树”算法中不能很好地发挥作用,如果您需要国际化或时区支持,则可以使用相当大的JavaScript包快速找到自己。

到目前为止,Chrome的开发工具突显了以下事实:使用Moment可能会导致性能下降。

所有这些都导致Moment维护人员将项目置于维护模式,并阻止Moment在以后的新项目中使用。

我们认识到许多现有项目可能会继续使用Moment,但是我们想阻止Moment在以后的新项目中使用。相反,我们想推荐当今在现代应用中使用的绝佳选择。

这使date-fns成为Moment.js的最佳替代产品之一。

安装

从库的第二版开始,安装date-fns的唯一方法是作为npm软件包。

npm install date-fns

或通过纱线:

yarn add date-fns

您可以将date-fns与CommonJS模块系统以及ES模块一起使用:

// CommonJS
const { lastDayOfMonth } = require('date-fns');

要么:

// ES Modules
import { lastDayOfMonth } from 'date-fns';

不幸的是,当前没有可用的date-fns CDN版本。在本GitHub问题中讨论了将其删除和恢复的可能性。但是,这并不是说您不能在浏览器中使用它,只是需要在工作流程中引入捆绑步骤。

如何捆绑date-fns在浏览器中使用

我假设您在计算机上安装了Node和npm。如果没有,请查阅我们有关安装Node的教程。

接下来,安装Parcel。这是一个捆绑程序(类似于Webpack),它将允许您捆绑JavaScript并将其提供给浏览器。

npm install -g parcel-bundler

接下来,使用package.json文件创建一个新项目。

mkdir datefns
cd datefns
npm init -y

如上所述安装date-fns库:

npm install date-fns

现在创建两个文件,index.htmlindex.js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>date-fns</title>
  </head>
  <body>
    <script src="index.js"></script>
  </body>
</html>
import { lastDayOfMonth } from 'date-fns';

const today = new Date();
console.log(lastDayOfMonth(today));

启动包裹的内置开发服务器:

parcel index.html

并导航到http:// localhost:1234。您不会在页面上看到任何显示,但是如果您打开浏览器的控制台。您应该已经记录了当月的最后一天。

关于部署,您可以运行:

parcel build index.js --experimental-scope-hoisting

使Parcel在dist文件夹中输出缩小且摇晃的捆绑包。

Date-fns基本用法

现在我们已经启动并运行,让我们看看date-fns可以做什么。

处理日期时最常见的任务之一是能够很好地格式化日期。我们可以使用date-fns 格式函数来做到这一点。

更改上面示例页面中的HTML,使其如下所示:

<body>
  <h1>The date today is <span></span></h1>
  <script src="index.js"></script>
</body>

index.js我们要导入的format函数中,我们可以传递今天的日期和格式字符串。然后,我们想将结果输出到页面。

import { format } from 'date-fns';

const today = new Date();
const formattedDate = format(today, 'dd.MM.yyyy');

document.querySelector('h1 > span').textContent = formattedDate;

当然,我们不限于一种dd.MM.yyyy格式,让我们尝试不同的方法:

const formattedDate = format(today, 'PPPP');

这将格式化像这样的输出:Wednesday, September 16th, 2020。您可以在docs中找到格式设置选项的完整列表。

变更地区

如果您拥有使用多种语言的网站,那么date-fns可使国际化日期和时间变得简单。让我们迎接德国客人:

<h1>Heute ist <span></span></h1>

在JavaScript文件中,我们可以导入德语语言环境并将其传递给format函数:

import { format } from 'date-fns';
import { de } from 'date-fns/locale';

const today = new Date();
const formattedDate = format(today, 'PPPP', { locale: de });

document.querySelector('h1 > span').textContent = formattedDate;

这将输出的东西沿着线:Heute ist Mittwoch, 16. September 2020

要求并以语言环境作为选项似乎很复杂,但与Moment.js缺省情况下使用所有语言环境使构建膨胀的方式不同,date-fns强制开发人员在需要时手动要求语言环境。

您可以通过查看node_modules/date-fns/locale项目中的文件夹来查看可用语言环境的列表。

不变性,纯正和简单

date-fns的卖点之一是其功能纯净,易于解释。这样可以使代码易于理解,并在出现问题时更易于调试。

让我使用Moment.js作为反示例进行演示。如前所述,Moment中的日期是可变的,这可能导致意外行为。

const moment = require('moment');
const now = new Date();
const mNow = moment(now);

mNow.add('day', 3);
console.log(mNow.toDate());
mNow.add(3, 'day');
console.log(mNow.toDate());

// 2020-09-19T10:08:36.999Z
// 2020-09-22T10:08:36.999Z

这里有几件事要注意。Moment的add函数对其接受其参数的顺序并不挑剔(尽管第一个方法现在将引发弃用警告)。但更令人困惑的是,如果add连续调用多次,将不会得到相同的结果,因为Moment对象是可变的:

mNow.add(3, 'day'); // add 3 days
mNow.add(3, 'day'); // adds 3 **more** days

现在,将其与date-fns进行比较,date-fns会将参数保持在一个顺序中,并且始终返回相同的结果,并Date为每次调用返回一个新对象。

import { addDays } from 'date-fns';

const today = new Date();
const threeDaysTime = addDays(3, today);
const sixDaysTime = addDays(threeDaysTime, 3);

console.log(today); // Wed Sep 16 2020 12:11:55 GMT+0200
console.log(threeDaysTime); // Sat Sep 19 2020 12:12:58 GMT+0200
console.log(sixDaysTime); // Invalid Date

还要注意,方法名如何更具表现力(addDays而不是仅仅add),保持事物的一致性,并使一种方法只能执行一件事情和一件事情。

比较日期

如果您查看SitePoint JavaScript频道上的帖子列表,则可以看到其中某些列表被列为在某个日期发布,而其他列表则被列为在X天前发布。福州小程序开发如果您尝试在原始 JavaScript中实现此功能可能会花费一些时间,但是使用date- fns则很容易 -只需使用formatDistance方法即可。

让我们比较两个不同的日期。

import { formatDistance } from 'date-fns';

const startDate = new Date(2020, 8, 16); // (Sep 16 2020)
const endDate = new Date(2020, 11, 25); // (Dec 25 2020)
const distanceInWords = formatDistance(startDate, endDate);

console.log(`It is ${distanceInWords} until Christmas`);
// It is 3 months until Christmas

请注意,使用JavaScript时,月份是从零开始的(例如,月份11 =十二月),但是天数是从1开始增加的。这一次又一次地使我绊倒。

处理日期集合

Date-fns有一些非常方便的帮助器方法,您可以使用它们以各种方式来操作日期集合。

订购日期收集

以下示例使用compareAsc将日期按升序排序。为此,如果第一个日期在第二个日期之后,则返回1;如果第一个日期在第二个日期之前,则返回-1;如果日期相等,则返回0。

import { compareAsc } from 'date-fns';

const date1 = new Date('2005-01-01');
const date2 = new Date('2010-01-01');
const date3 = new Date('2015-01-01');
const arr = [date3, date1, date2];
const sortedDates = arr.sort(compareAsc);

// [ 2005-01-01, 2010-01-01, 2015-01-01 ]

如您所见,日期现在按升序排列。

与之对应的方法compareAsc是compareDesc。

import { compareDesc } from 'date-fns';
...
const sortedDates = arr.sort(compareDesc);
// [ 2015-01-01, 2010-01-01, 2005-01-01 ]

生成两个日期之间的日期

要生成两个日期之间的日期,可以使用我们之前遇到的addDays方法,以及eachDayOfInterval帮助器,该方法返回指定范围内的日期数组。

import { addDays, eachDayOfInterval } from 'date-fns';

const today = new Date();
const aWeekFromNow = addDays(today, 7);
const thisWeek = eachDayOfInterval(
  { start: today, end: aWeekFromNow },
);

console.log(thisWeek);

/*
[
  Wed Sep 16 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Thu Sep 17 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Fri Sep 18 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Sat Sep 19 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Sun Sep 20 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Mon Sep 21 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Tue Sep 22 2020 00:00:00 GMT+0200 (Central European Summer Time),
  Wed Sep 23 2020 00:00:00 GMT+0200 (Central European Summer Time)
]
*/

寻找最近的日期

可以使用closestTo方法在日期数组中查找最接近某个日期的日期。此代码段来自上一个示例:

import { addDays, eachDayOfInterval, closestTo } from 'date-fns';
...
const christmas = new Date(2020, 11, 25);
const closestToChristmasDate = closestTo(christmas, thisWeek);

console.log(closestToChristmasDate);
// Wed Sep 23 2020 00:00:00 GMT+0200 (Central European Summer Time)

如果您想获取数组的索引,则还有closeestIndexTo方法。

验证日期

我要看的最后一个助手是isValid方法,顾名思义,该方法检查给定日期是否有效。

但是,由于JavaScript处理日期的方式,因此需要注意一些陷阱:

import { isValid } from 'date-fns';

const invalidDate = new Date('2020, 02, 30');
console.log(isValid(invalidDate));
// true, lol, wut?

您会以为上面的代码段应该输出false,这是可以原谅的,因为2020年2月30日显然是无效的日期。要了解正在发生的事情,请new Date('2020, 02, 30')在浏览器的控制台中输入。您会发现Sun Mar 01 2020回来了-JavaScript从2月底开始花费了额外的时间,并将其变成3月1日(当然是有效日期)。

要解决此问题,我们可以在检查日期之前解析日期:

import { isValid, parse } from 'date-fns';

const validDate = parse('29.02.2020', 'dd.MM.yyyy', new Date());
const invalidDate = parse('30.02.2020', 'dd.MM.yyyy', new Date());

console.log(validDate);
// Sat Feb 29 2020 00:00:00 GMT+0100 (Central European Standard Time)

console.log(invalidDate);
// Invalid Date

console.log(isValid(validDate));
// true

console.log(isValid(invalidDate));
// false

可以轻松地将其提取到一个小助手方法中,该方法对于例如验证表单中的用户输入很有用。

时区

date-fns的一个缺点是它目前没有像Moment.js那样的任何时区帮助程序功能,而是返回了代码在其上运行的本地时区。

这个堆栈溢出的答案提供了一些本机Date对象如何实际不存储“实时区域”数据的背景知识。在该线程中,您会注意到他们提到了一种在JavaScript中原生设置时区的方法。这不是一个全面的解决方案,但是它适用于仅需要输出转换(从UTC或本地时间到特定时区)的许多情况。

new Date().toLocaleString("en-US", {timeZone: "America/New_York"});

时区实际上是一个要解决的复杂问题,这就是为什么MomentJS有一个单独的库。有计划在date-fns中添加时区支持,但是在撰写本文时,这仍在进行中。

但是,npm上有一个可用的软件包(基于对date-fns的未合并提取请求),该软件包使用Intl API添加了对date-fns v2.0.0的时区支持。每周下载14万次,这似乎很受欢迎,但是在撰写本文时,它已经有几个月没有更新了。

就是说,这是您可能会使用的方式:

npm i date-fns-tz
import { format, utcToZonedTime } from 'date-fns-tz';

const today = new Date(); // Wed Sep 16 2020 13:25:16
const timeZone = 'Australia/Brisbane'; // Let's see what time it is Down Under
const timeInBrisbane = utcToZonedTime(today, timeZone);

console.log(`
  Time in Munich: ${format(today, 'yyyy-MM-dd HH:mm:ss')}
  Time in Brisbane: ${format(timeInBrisbane, 'yyyy-MM-dd HH:mm:ss')}
`);

// Time in Munich: 2020-09-16 13:26:48
// Time in Brisbane: 2020-09-16 21:26:48

结论

Date-fns是一个很棒的小程序库,它为您提供了很多帮助程序方法,可用于在JavaScript中使用日期和时间。目前正在积极开发中,并且Moment.js已进入维护模式,这使其成为Moment.js的理想替代品。

我希望本文能给您足够的理解和启发,以便您进行检查并开始在自己的项目中使用它。

如何用React Hooks和Context API替换Redux

詹姆斯·希伯德

目前,我在SitePoint工作,担任其JavaScript中心的编辑器和各种书籍的技术编辑器(例如,JavaScript:Ninja的新手和Jump Start Vue.js)。我还以网络管理员和自由职业者的Web开发人员的身份工作,在这里我花了大量的时间在Rails应用程序上工作。

Style React组件:7种比较方法

由  普利文库马尔

的JavaScript

 

我一直在办公室里与几个开发人员合作开发React项目,他们具有不同的React经验水平。本文源于一个问题,其中一个问题是如何最好地设计React组件的样式。有多种方式来对React组件进行样式设置。选择正确的组件样式方法并不是绝对的完美选择。这是一个特定的决定,应该适合您的特定用例,个人喜好,尤其是您工作方式的体系结构目标。例如,我使用Noty来利用React JS中的通知,并且样式也应该能够处理插件。

我回答这个问题的一些目标包括:

  • 全局命名空间
  • 依存关系
  • 可重用性
  • 可扩展性
  • 死代码消除

似乎有多种样式化React组件的样式,这些样式在行业中广泛用于生产级别的工作:

  • 内联CSS
  • 普通的CSS
  • JS库中的CSS
  • CSS模块
  • Sass和SCSS
  • 风格化

对于每种方法,我都会研究对依赖项的需求,难度级别以及该方法是否真的是一种好方法。

内联CSS

  • 依赖关系:
  • 难度:容易
  • 方法:最差

我认为没有人需要内联CSS的介绍。这是直接使用HTML或JSX发送到元素的CSS样式。您可以在React组件中包含CSS的JavaScript对象,尽管有一些限制,例如驼峰式的包含连字符的任何属性名称。您可以使用JavaScript对象以两种方式对React组件进行样式设置,如示例所示。

import React from "react";

const spanStyles = {
  color: "#fff",
  borderColor: "#00f"
};

const Button = props => (
  <button style={{
    color: "#fff",
    borderColor: "#00f"
  }}>
    <span style={spanStyles}>Button Name</span>
  </button>
);

普通CSS

  • 依赖关系:
  • 难度:容易
  • 方法:

常规CSS是一种通用方法,可以说比内联CSS更好。可以将样式导入到任意数量的页面和元素中,而与直接应用于特定元素的嵌入式CSS不同。普通的CSS具有许多优点,例如本机浏览器支持(不需要依赖项),无需学习其他工具,也没有供应商锁定的危险。

您可以维护任意数量的样式表,并且在需要时可以更轻松地更改或自定义样式。但是,如果您正在从事一个涉及很多人的大型项目,那么常规CSS可能是一个主要问题,尤其是在没有一致同意的CSS样式指南编写时。

/* styles.css */

a:link {
  color: gray;
}
a:visited {
  color: green;
}
a:hover {
  color: rebeccapurple;
}
a:active {
  color: teal;
}
import React from "react";
import "styles.css";

const Footer = () => (
  <footer>
    &copy; 2020
    <a href="https://twitter.com/praveenscience">Find me on Twitter</a>
  </footer>
);

export default Footer;

更多信息

您可以在W3C的Learning CSS页面中了解有关常规CSS用法的更多信息。有很多游乐场,例如JS Bin,JSFiddle,CodePen和Repl.it,您可以在其中进行实时试用并实时获得结果。

CSS-in-JS

CSS-in-JS是一项使您能够使用JavaScript设置组件样式的技术。解析此JavaScript时,将生成CSS(通常作为<style>元素)并将其附加到DOM中。

这种方法有几个好处。例如,默认情况下,生成的CSS具有范围,这意味着对组件样式的更改不会影响该组件之外的任何其他内容。这有助于防止样式表随着时间的流逝而膨胀。如果删除组件,则会自动删除其CSS。

另一个优点是您可以利用JavaScript的功能与CSS进行交互。例如,您可以使用JavaScript创建自己的帮助器函数,然后直接在CSS中使用它们来修改代码。

接下来,我们将看两个可用于在React应用程序中实现此功能的库。

JSS

  • 依存关系: react-jss
  • 难度:容易
  • 做法:体面

JSS称自己为“ CSS的创作工具,使您可以使用JavaScript以声明性,无冲突且可重用的方式描述样式”。它与框架无关,但是在样式化React组件时,React-JSS使用新的Hooks API将JSS与React集成在一起。

import React from "react";
import {render} from "react-dom";
import injectSheet from "react-jss";

// Create your styles. Since React-JSS uses the default JSS preset,
// most plugins are available without further configuration needed.
const styles = {
  myButton: {
    color: "green",
    margin: {
      // jss-expand gives more readable syntax
      top: 5, // jss-default-unit makes this 5px
      right: 0,
      bottom: 0,
      left: "1rem"
    },
    "& span": {
      // jss-nested applies this to a child span
      fontWeight: "bold" // jss-camel-case turns this into 'font-weight'
    }
  },
  myLabel: {
    fontStyle: "italic"
  }
};

// Define the component using these styles and pass it the 'classes' prop.
const Button = ({ classes, children }) => (
  <button className={classes.myButton}>
    <span className={classes.myLabel}>{children}</span>
  </button>
);

// Finally, inject the stylesheet into the component.
const StyledButton = injectSheet(styles)(Button);

const App = () => <StyledButton>Submit</StyledButton>
render(<App />, document.getElementById('root'))

更多信息

您可以在JSS 官方文档中了解有关此方法的更多信息。还有一种使用其REPL(read-eval-print循环)进行尝试的方法。

样式组件

  • 依存关系: styled-components
  • 难度:中等
  • 做法:体面

样式化组件是另一个实现上述CSS-in-JS技术的库。它利用标记的模板文字(在两个反引号之间包含实际的CSS代码)来对组件进行样式设置。很好,因为您随后可以从另一个项目(或Web上的其他任何地方)复制/粘贴CSS代码,并使一切正常。没有像其他一些库那样转换为驼峰大小写或JS对象语法。

样式化组件还删除了组件和样式之间的映射。从他们的文档中可以看出,这意味着在定义样式时,实际上是在创建一个普通的React组件,并在其中附加了样式。与最终使用<Layout>组件<div>名称而不是类名称“ layout” 的代码相比,这使您的代码更简洁易懂。

道具可以用来将样式化的组件样式化,就像传递给正常的React组件一样。使用道具代替CSS中的类,并动态设置属性。

import React from "react";
import styled, { css } from "styled-components";

const Button = styled.button`
  cursor: pointer;
  background: transparent;
  font-size: 16px;
  border-radius: 3px;
  color: palevioletred;
  margin: 0 1em;
  padding: 0.25em 1em;
  transition: 0.5s all ease-out;
  ${props =>
    props.primary &&
    css`
      background-color: white;
      color: green;
      border-color: green;
    `};
`;

export default Button;

更多信息

样式化组件具有详细的文档,该站点还提供了实时编辑器,您可以在其中试用代码。在styled-components:Basics上获得有关styled-components的更多信息。

样式组件替代

根据您的需要,还有许多其他CSS-in-JS库可供考虑。一些受欢迎的例子包括:

  • 情绪是更小,更快比风格的组件。如果您已经在使用样式化组件,则可能无需急于更改库-它的维护者说这正在缩小差距。
  • 对于希望最大化Core Web Vitals分数的开发人员而言,Linaria是一个受欢迎的选择。Linaria的核心区别在于它是一个零运行时库-在构建期间,所有CSS-in-JS都提取到CSS文件中。

CSS模块

  • 依存关系: css-loader
  • 难度:艰难(使用加载程序配置)
  • 方法:更好

如果您曾经觉得CSS全局范围问题占用了大部分时间,而您必须查找特定样式的内容,或者是否摆脱CSS文件使您紧张地想知道是否可能破坏代码中的其他内容基地,我感觉到你。

CSS模块通过确保组件的所有样式都放在一个地方并且仅应用于该特定组件来解决此问题。这无疑解决了CSS的全局范围问题。它们的组合功能可充当代表应用程序中状态之间共享样式的武器。它们与Sass中的mixins相似,这使得可以组合多组样式。

import React from "react";
import style from "./panel.css";

const Panel = () => (
  <div className={style.panelDefault}>
    <div className={style.panelBody}>A Basic Panel</div>
  </div>
);

export default Panel;
.panelDefault {
  border-color: #ddd;
}
.panelBody {
  padding: 15px;
}

请注意,如果您使用的是Create React App,它开箱即用地支持CSS模块。否则,您将需要webpack和几个使webpack捆绑CSS文件的加载程序。Robin Wieruch 在这方面有很棒的教程。

Sass和SCSS

  • 依存关系: node-sass
  • 难度:容易
  • 方法:最佳

Sass声称它是世界上最成熟,稳定,最强大的专业级CSS扩展语言。它是CSS预处理器,它在常规CSS中添加了一些特殊功能,例如变量,嵌套规则和混合(有时称为“语法糖”)。目的是使编码过程更简单,更有效。与其他编程语言一样,Sass允许使用变量,嵌套,部分,导入和函数,这为常规CSS增加了超能力。

Sass样式表可以通过多种方式导出并在React项目中使用。如您所料,Create React App开箱即用地支持Sass。如果您使用的是webpack,则需要使用sass-loader,也可以只使用sass --watch命令。

我们将在本文结尾处介绍如何将Sass与Create React App一起使用。

$font-stack: 'Open Sans', sans-serif;
$primary-color: #333;

body {
  font: 100% $font-stack;
  color: $primary-color;
}

更多信息

在Sass的官方文档:语法上很棒的样式表上了解有关使用各种编程语言使用和安装Sass的更多信息。如果您想尝试一下,可以使用一项名为SassMeister的服务-The Sass Playground!在这里您可以体验Sass和SCSS的不同功能。

  • 依赖关系:lessless-loader
  • 难度:容易
  • 方法:

Less(Leaner Style Sheets)是一种开源的动态预处理器样式表语言,可以将其编译为CSS并在客户端或服务器端运行。它从CSS和Sass中获得灵感,类似于SCSS。一些显着的区别包括@以Less 开头的符号和以$Sass结尾的变量的变量。

@pale-green-color: #4D926F;

#header {
  color: @pale-green-color;
}
h2 {
  color: @pale-green-color;
}

更多信息

您可以从官方文档开始使用Less,还有LESSTESTER,这是一个Less Sandbox,可以将Less代码转换为CSS。

风格化

  • 依赖关系:stylable@stylable/webpack-plugin
  • 难度:难度
  • 方法:更好

如果您不是CSS-in-JS的最大支持者,那么Stylable可能适合您。它是一个预处理器,使您能够将样式范围限定到组件,以使它们不会泄漏或与应用程序中其他位置的其他样式冲突。它具有一些方便的功能-例如定义自定义伪类的功能 -以便您可以根据状态将样式应用于组件。它也受到TypeScript的启发,该项目的主页上注明:

我们想要给CSS一个类型系统-为CSS做TypeScript为JavaScript做的事情。

在将Stylable与React集成时,他们提供了方便的指南。还有一个create-stylable-app项目,它将使用Stylable作为其样式解决方案来初始化基于React的Web应用程序。

@namespace "Example1";

/* Every Stylable stylesheet has a reserved class called root
that matches the root node of the component. */
.root {
  -st-states: toggled, loading;
}
.root:toggled { color: red; }
.root:loading { color: green; }
.root:loading:toggled { color: blue; }
/* CSS output*/
.Example1__root.Example1--toggled { color: red; }
.Example1__root.Example1--loading { color: green; }
.Example1__root.Example1--loading.Example1--toggled { color: blue; }

更多信息

样式更多。有关入门的官方文档提供了详细说明。

让我们的手变脏

有这么多的选择,我变得很脏,然后一一尝试。感谢Git,我能够进行版本控制并比较所有内容,以查看哪个选项是赢家。我能够弄清我使用了多少依赖关系以及使用复杂样式时工作流程如何。除了普通的CSS,CSS模块和SCSS之外,我在所有其他方面都有些挣扎。

办公室工作

CSS Modules可以帮助进行导入和其他操作,但是当我尝试使用它时,对其扩展的支持并不多。当我使用多个CSS类和层次结构时,CSS模块出现问题。唯一的好处是您不会遇到任何CSS冲突,但是缺点是CSS代码非常庞大。这有点像BEM方法。

除此之外,处理伪元素和状态真是地狱。有时,当我尝试从另一个文件导入另一个类的内容时,CSS模块甚至无法编译。我更愿意在SCSS中使用mixins,但是不幸的是,CSS模块在这方面仍然很基础。composes这里的关键字几乎对我没有用。这是我个人面临的巨大弊端。这可能是我的错,因为我没有正确使用它,但是即使是真正的案例,它也不起作用。

个人项目

我将SCSS作为使用React样式的下一个尝试。幸运的是,它奏效了。将SCSS与CSS模块进行比较可以使我获得深刻的见解。它们之间有很多共同点。这里的一个收获是我已经使用过SCSS,并且对它非常满意,因为它类似于CSS,但是具有超能力。使用Create React App时,我唯一需要做的就是安装另外一个依赖项node-sass,而别无其他。请勿触摸webpack配置或从Create React App弹出React JS应用程序。

通过查看CSS模块的功能,我在SCSS和CSS模块之间进行了浅浅的比较,发现我在SCSS中具有大多数功能。谈论组成-这是我的团队选择CSS模块的主要原因-我们可以在SCSS中使用@mixin@include,它们工作得很好。我还没有看到CSS模块与JavaScript进行交互,因此SCSS也是一样-没有与JavaScript部分的交互。函数的循环和包含是SCSS特有的,因此我考虑将SCSS用于我的个人项目和办公室中的一个新项目。

最终获胜者是…

显然,SCSS是这里的绝对赢家。与CSS模块相比,SCSS提供了许多现成的新功能。现在,我们将对SCSS进行深入的分析,它会更好,以及为什么要在下一个项目中使用SCSS。

SCSS获胜:深入分析

我喜欢SCSS,因为它提供了许多功能。第一件事是它与CSS非常相似。您无需学习任何新知识即可了解SCSS。如果您了解CSS,那么您可能会了解Sass。Sass带有两种不同的语法:Sass本身和SCSS,使用更多。SCSS语法与CSS兼容,因此您只需将.css文件重命名为即可.scss。当然,通过这样做,您并没有使用Sass提供的任何超级能力和功能,但至少您意识到,您不需要花费数小时就可以开始使用Sass。从这个起点开始,您将能够随时学习Sass语法。

您可以前往Sass Basics入门并使用这些基础知识。就React而言,为项目设置Sass支持并开始使用SCSS进行样式设置非常简单。与任何CSS预处理器一样,使用SCSS的下一个优点是能够使用变量。变量使您可以存储一个值或一组值,并在您的Sass文件中重复使用这些变量,次数不限,次数不限。简单,强大且有用。这有助于对产品或应用程序进行主题设置,并根据客户需求对其进行样式设置,而无需做很多事情,除了在此处和此处切换少量代码外。

嵌套在CSS(或SCSS)中是Sass可以提供的最好的功能之一。Sass允许您使用嵌套语法,该语法是另一段执行更广泛功能的代码中包含的代码。在Sass中,嵌套可提供一种更干净的目标元素定位方式。换句话说,您可以使用CSS选择器嵌套HTML元素。将代码与Sass嵌套在一起有很多好处,主要的好处就是SCSS的可维护性和可读性。我们都已经在代码中听说过DRY概念,但是在CSS中,这避免了多次重写选择器的需要。这也有助于更轻松地维护代码。

使用局部函数的能力很棒。您可以在任何地方将SCSS拆分为局部,并在需要时将其包括在内。您还可以将它们拆分为mixins并传递一些参数以提供完全不同的CSS。使用变量很棒,但是如果样式表中有重复的代码块怎么办?那就是mixins发挥作用的时候。Mixins就像其他编程语言中的函数一样。它们返回一个值或一组值,并可以采用包括默认值在内的参数。请注意,Sass也具有functions,所以请不要将mixin与function混淆。

在React中使用SCSS

随着最近发布的升级版Create React App的出现,我们有了很多新工具可以使用。我很高兴内置了Sass,因为我们曾经不得不在文件夹结构中直接.scss编译文件并写入.css文件。您可能会担心在React中使用Sass。用CSS-in-JS库(如样式组件或美之女神)编写样式不是更聪明的方法吗?我相信为Create React App添加Sass支持将对React初学者有很大帮助。

以下是一些步骤:

  1. 让我们从安装Create React App开始。您可以通过在npm install -g create-react-app全局范围内运行或通过npx create-react-app立即下载并调用它来做到这一点,从而使已安装的软件包不会在全局变量中出现。您可以在此处找到有关npx的更多信息。
  2. 使用创建一个新的React项目,create-react-app <app-name>然后切换到该目录。
  3. 使用安装node-sass依赖npm install node-sass --save。这将编译您scsscss
  4. 就这样-我们完成了。我们可以通过将src/App.css文件更改为src/App.scssfile并更新src/App.js以导入来测试配置。然后,我们可以尝试一些很酷的Sass / SCSS功能。

常见例子

这是在SCSS中使用变量的一种方法:

$blue: #004BB4;
$ubuntu-font: 'Ubuntu', 'Arial', 'Helvetica', sans-serif;
$nunito-font: 'Nunito', 'Arial', 'Helvetica', sans-serif;

创建变量后,可以在需要的地方使用它们,如下所示:

h1 {
  font: $ubuntu-font;
  color: $blue;
}
a {
  font: $nunito-font;
  background-color: $blue;
  padding: 6px;
}

编译SCSS文件时,Sass编译器将处理您在源文件中使用的变量,将变量名替换为其存储的值。更改颜色的值就像更新变量内容并重新编译一样快。在您喜欢的文本编辑器中使用“查找和替换”来更改CSS文件中的颜色的日子已经一去不复返了。

我之前介绍的一个有价值的功能是SCSS的“嵌套”功能。一个例子可以在这里展示:

<ul class="navbar">
  <li><a href="/">Item <span>1</span></a></li>
  <li><a href="/">Item <span>2</span></a></li>
  <li><a href="/">Item <span>3</span></a></li>
  <li><a href="/">Item <span>4</span></a></li>
  <li><a href="/">Item <span>5</span></a></li>
</ul>
.navbar {
  font: $ubuntu-font;
  color: $blue;
  li {
    margin-left: 1rem;
    a {
      padding: 5px;
      font-size: 1.5rem;
      span {
        font-weight: 600;
      }
    }
  }
}

但是,请注意嵌套得太深不是一个好习惯。嵌套的深度越深,Sass文件将变得越冗长,并且已编译的CSS可能会越大,因为在编译时嵌套会变平。因此,过度使用嵌套会创建难以维护的过于具体的CSS规则。选择器有可能无法重用,并且也存在性能问题。嵌套选择器将创建一个长的CSS选择器字符串,最终将生成一个更大的CSS文件。

包起来

在本文中,我研究了在React应用程序中样式化组件的几种方法。然后,我比较并对比了这些方法,检查了它们的优缺点。最后,我演示了如何在Create React App项目中使用Sass(我的首选React应用程序样式方法)。

Sass是CSS预处理程序,CSS预处理程序将保留下来。它们通过为您提供一组强大的功能来扩展基本的CSS功能,这些功能将立即提高您的生产率。我提到了一些好处,但也有更多的人,如继承,功能,控制指令,并般的表情if()for()或者while(),数据类型,内插,等等。

成为Sass大师可能需要一些时间。您所需要做的就是查看Bootstrap Sass文件,看看Sass如何变成一件复杂的事情。但是,学习基础知识并为您的项目进行设置是您今天可以开始的事情。

版权所有:https://www.eraycloud.com 转载请注明出处