React的10种有效的设计模式

前端之家收集整理的这篇文章主要介绍了React的10种有效的设计模式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

设计React的10种模式

本文翻译自10 React mini-patterns。这篇文章mrcode翻译, 如果哪里翻译的不恰当或有错误的地方,欢迎指出。 同时也希望大家关注我的博客。 关注我的账号。

在过去的几年里,我已经做了许多看起来挺不错的React项目。
在这个神奇的旅程中,一些模式出现过很多次,我发现我一次又一次地重复着这些模式。

什么是模式?

这些模式是我想在学习React第一天就知道的事情。
所以如果今天是你第一天学习React,你是如此的幸运。

或者你并不幸运。只有一种方法可以决定你是否是幸运的...

这是一个长长的列表,所以你可以跳过无聊的一些模式, 比如:3,6,8,10。

1. Sending data down and up

我建议大家新学习React的一件事是传递信息的模式(信息可以是对象,字符串等)和传递方法下来允许子组件传递信息给父组件。

就像把一包芯片和一个对讲机送到地下被困的矿工一样。

图片怎么样?
下面的事情是这种模式的最简单的形式。

父组件在左边,子组件在右边。
你可以认为连接这些组件的两个props允许信息在两者之间的任一方向上流动。

被称为items将被传递给子组件, deleteItem将提供给子组件一种方案来发送信息给父组件。

这不是一个真正的模式。剩下的肯定都是模式。我承诺。

2. Fixing HTML’s inputs

React和web组件的一个伟大的事情是,如果在html中的东西不能按你想要的方式工作,你可以解决它。

如果你考虑允许用户输入的不同元素,你很快就会看到这些元素的命名是荒谬的,几乎是鲁莽的。

如果我建立一个将有很多用户输入的网站,我做的第一件事之一是解决这个问题。

还有更多的改进:

@H_403_56@
  • 输入应该通过onChange方法返回一个值,而不是一个JavaScript事件实例,对不?

  • 你可以进一步确保在onChange返回的数据类型和传递的数据类型相匹配。如果typeof props.value是number,然后将e.target.value回到一个数字,然后再次发出数据。

  • 一组单选按钮在功能上与<select>一样
    它是搞砸了,以一种完全不同的方式来对待它们,唯一的区别是UI。

  • 也许你的应用程序有一个单一的<PickOneFromMany />组件,并传递ui =“radioui =“dropDown”

    关键是不要像我这样做。
    关键是要使它们成为你自己的 - 你不需要继续使用HTML的用户输入元素的坑爹性质。

    3. Binding labels to inputs with unique IDs

    关于输入...如果你关心你的用户,你将通过id / for组合将<label>元素绑定到你的<input>

    但是你不想为你定义的每个输入想出一些聪明和独特的id,谁有时间呢?我不知道你,但我有山羊的视频观看。

    提示:如果您的航班上有一个尖叫的孩子,闭上眼睛,假装您在YouTube上观看的山羊听起来像人类的视频,烦人的声音就会变得很热闹。)

    您可以为每个输入/标签生成随机ID,但是客户端呈现的HTML将与您呈现的服务器呈现的HTML不匹配。这并不是一个好的解决方
    所以,你可以创建一个小的模块,给出一个递增的ID,并在输入组件中使用它,像这样:

    class Input extends React.Component {
      constructor(props) {
        super(props);
        this.id = getNextId();
        
        this.onChange = this.onChange.bind(this);
      }
      
      onChange(e) {
        this.props.onChange(e.target.value);
      }
      
      render() {
        return (
          <label htmlFor={this.id}>
            {this.props.label}
            
            <input
              id={this.id}
              value={this.props.value} 
              onChange={this.onChange}
              />
          </label>
        );
      }
    }

    如果getNextId()每次只是增加一个数字,然后在服务器上渲染时,这个数字会继续上升和起来,最终达到无穷大。因此,您需要在每次呈现应用程序时重置该数字(对于每个网络请求)。

    你可以在你的应用程序的入口点,使用一个简单的resetId()或任何你认为最好的名称

    考虑到所有这些,你的超级幻想模块可能看起来像这样:

    let count = 1;
    
    export const resetId = () => {
      count = 1;
    }
    
    export const getNextId = () => {
      return `element-id-${count++}`;
    }

    4. Controlling CSS with props

    当你想在不同的实例(例如'primary'和'secondary'按钮)应用不同的CSS,你可以传递道具来控制要应用的CSS。

    这看起来超级简单的表面,但让我向你保证有很多错误方法来做到这一点(我已经尝试过他们!)。

    有 - 我估计 - 三种不同的方式,你可以控制应用于组件的CSS。

    使用标志

    也许你的一些按钮有圆角,但这不直接对应于您定义的主题

    在这种情况下,你可以坐下你的设计师,并有一致性谈话,或创建一个布尔的道具,可能看起来像这样:

    <Button theme =“secondary”rounded> Hello </ Button>

    就像HTML的二进制属性一样,你不需要做round = {true}

    设置值

    在某些情况下,您可能希望直接传递CSS属性的值(在组件中将其设置为内联样式)。

    <Icon width =“25”height =“25”type =“search”/>

    一个例子

    假设您正在创建链接组件。你通过你的网站的设计和工作,有三个不同的主题,有时他们有一个下划线,有时他们不。

    下面是我将如何设计该组件:

    const Link = (props) => {
      let className = `link link--${props.theme}-theme`;
      
      if (!props.underline) className += ' link--no-underline';
    
      return <a href={props.href} className={className}>{props.children}</a>;
    };
    
    Link.propTypes = {
      theme: PropTypes.oneOf([
        'default',// primary color,no underline
        'blend',// inherit surrounding styles
        'primary-button',solid block
      ]),underline: PropTypes.bool,href: PropTypes.string.isrequired,children: PropTypes.oneOfType([
        PropTypes.element,PropTypes.array,PropTypes.string,]).isrequired,};
    
    Link.defaultProps = {
      theme: 'default',underline: false,};

    增加CSS...

    .link--default-theme,.link--blend-theme:hover {
      color: #D84315;
    }
    
    .link--blend-theme {
      color: inherit;
    }
    
    .link--default-theme:hover,.link--blend-theme:hover {
      text-decoration: underline;
    }
    
    .link--primary-button-theme {
      display: inline-block;
      padding: 12px 25px;
      font-size: 18px;
      background: #D84315;
      color: white;
    }
    
    .link--no-underline {
      text-decoration: none;
    }

    你可能已经注意到链接 - 无下划线的选择器是没必要存在的, 因为他双重否定了。

    故事时间:我曾经认为写CSS更少的CSS是目标,但它不是。我宁愿有一些双重否定和多选择器规则集,如果它的意思是样式以一个很好的分层方式应用的话。

    我相信我以前说过,但缩放网站最困难的事情是CSS。 JavaScript很容易,但是随意使用CSS使你很遭罪 - 一旦你开始混乱,这是不容易中途修改解决的。

    真实的事实:CSS的特异性是网络开发人员死亡的第一原因。如果你在一台大型计算机上,请查看顶部导航栏中的小通知图标的CSS。

    这个通知图标是由很多CSS样式组合在一起的。很复杂。

    二十三条规则。

    这不包括继承自十一个其他规则的样式。行高单独被覆盖九次。

    如果line-height是一只猫,它现在已经死了。

    这不能令人愉快地维护。

    有了React,我们可以做得更好。我们可以仔细设计哪些类应用于我们的组件。我们可以删除全局样式和移动它所有在我们的Button.scss。我们可以消除对文件的特异性和顺序的所有依赖。

    附注: 我梦想着有一天游览器对于样式没有自己的看法(意思就是所有游览器都变得统一, 完全去IE化-。-)。

    5. The switching component

    切换组件是呈现最多的组件之一。

    这可能是一个显示多个页面之一的<Page>组件。或选项卡集中的选项卡,或模态组件中的不同模态。

    我曾经使用switch语句,进一步到实际传入我想要渲染的组件。然后从组件本身导出对组件的引用(命名为exports,然后作为组件上的属性)。

    真是一堆可怕的想法!

    我现在的方法是使用一个对象传递props给Page组件。

    import HomePage from './HomePage.jsx';
    import AboutPage from './AboutPage.jsx';
    import UserPage from './UserPage.jsx';
    import FourOhFourPage from './FourOhFourPage.jsx';
    
    const PAGES = {
      home: HomePage,about: AboutPage,user: UserPage,};
    
    const Page = (props) => {
      const Handler = PAGES[props.page] || FourOhFourPage;
      
      return <Handler {...props} />
    };
    
    Page.propTypes = {
        page: PropTypes.oneOf(Object.keys(PAGES)).isrequired,};

    PAGES对象的key可以在prop类型中使用,以捕获dev时间错误

    然后,我们当然会使用这样<page page =“home”/>。

    如果你用key替换home,about和user分别用/, /about和/user,你差不多就是个路由器了。

    (未来的想法:再见 react-router。)

    6. Reaching into a component

    如果您正在寻找一个简单的方法来请求您的用户输入信息,那么你可以添加自动对焦到输入组件, 当用户一个页面的时候。这种设计仅仅适用于登陆操作就在主页面内执行, 而不是单独弹出一个模态窗口。

    你可以通过给输入组件一个id,然后使用document.getElementById('user-name-input')。focus()来将用户的焦点集中在输入组件上。

    这工作,但不是正确的方式。在你的应用程序中依靠两个字符串匹配的事情越少越好。
    这可以正常的工作, 但确不是最好的方式。 在你的代码中依靠两个字符串匹配的事情越少越好。

    幸运的是,有一个非常容易的方法来做到这一点“正确”:

    class Input extends Component {
      focus() {
        this.el.focus();
      }
      
      render() {
        return (
          <input
            ref={el=> { this.el = el; }}
          />
        );
      }
    }

    真是酷炫屌炸天! 一个具有focus()方法的输入组件,用于聚焦HTML元素。

    在父组件中,我们可以获得对Input组件的引用并调用其focus()方法

    class SignInModal extends Component {
      componentDidMount() {
        this.InputComponent.focus();
      }
      
      render() {
        return (
          <div>
            <label>User name: </label>
            <Input
              ref={comp => { this.InputComponent = comp; }}
            />
          </div>
        )
      }
    }

    注意,当在组件上使用ref时,它是对组件(而不是底层元素)的引用,因此您可以访问其方法

    7. Almost-components

    假设您正在构建一个组件,以便您可以搜索人员。在您输入时,您会看到一个可能匹配的名称和照片列表。这样的东西。

    (我正在寻找政治讽刺,因为我像大家一样,对其他人对政治的看法极为感兴趣。)

    当设计此组件时,您可能会想到自己:该列表中的每个项目都是自己的SearchSuggestion组件?它真的只有几行HTML和CSS,也许不是?但我曾经被告知“如果有疑问,创造另一个组件”。

    哦,我的,这是相当稀烂的一个泡菜,不是吗?

    如果我是做这个,我不会有一个单独的组件。相反,只是一个renderSearchSuggestion方法返回每个条目的适当的DOM。然后结果就是下面的代码示例这样:

    const SearchSuggestions = (props) => {
      // renderSearchSuggestion() behaves as a pseduo SearchSuggestion component
      // keep it self contained and it should be easy to extract later if needed
      const renderSearchSuggestion = listItem => (
        <li key={listItem.id}>{listItem.name} {listItem.id}</li>
      );
      
      return (
        <ul>
          {props.listItems.map(renderSearchSuggestion)}
        </ul>
      );
    }

    如果事情变得更复杂,或者您想在其他地方使用此组件,则应该能够将代码复制/粘贴到新组件中。

    不要过早组件化。组件不像茶匙;你可以有太多。(意思组件可以随便复制, 但是茶匙不行)

    8. Components for formatting text

    当我第一次开始使用React时,我想到组件应该是一个大东西,一种分组DOM的结构块的方法。但这样组件表现的很一般。

    这里是一个<Price>组件,它接受一个数字,并返回一个漂亮的字符串,有或没有小数和一个'$'符号。

    const Price = (props) => {
        const price = props.children.toLocaleString('en',{
          style: props.showSymbol ? 'currency' : undefined,currency: props.showSymbol ? 'USD' : undefined,maximumFractionDigits: props.showDecimals ? 2 : 0,});
        
        return <span className={props.className}>{price}</span>
    };
    
    Price.propTypes = {
      className: React.PropTypes.string,children: React.PropTypes.number,showDecimals: React.PropTypes.bool,showSymbol: React.PropTypes.bool,};
    
    Price.defaultProps = {
      children: 0,showDecimals: true,showSymbol: true,};
    
    const Page = () => {
      const lambPrice = 1234.567;
      const jetPrice = 999999.99;
      const bootPrice = 34.567;
      
      return (
        <div>
          <p>One lamb is <Price className="expensive">{lambPrice}</Price></p>
          <p>One jet is <Price showDecimals={false}>{jetPrice}</Price></p>
          <p>Those gumboots will set ya back <Price showDecimals={false} showSymbol={false}>{bootPrice}</Price> bucks.</p>
        </div>
      );
    };

    正如你可以看到,我使用强大的Intl字符串格式化库,这里有一个链接到他们的网站。

    我应该指出(在一些朋克之前),这不是一行代码的保存。你可以很容易地使用函数来做到这一点。 (当然,组件只是具有不同形状括号的函数。)

    这是更少的代码,但对我的眼睛,不太好:

    function numberToPrice(num,options = {}) {
        const showSymbol = options.showSymbol !== false;
        const showDecimals = options.showDecimals !== false;
        
        return num.toLocaleString('en',{
          style: showSymbol ? 'currency' : undefined,currency: showSymbol ? 'USD' : undefined,maximumFractionDigits: showDecimals ? 2 : 0,});
    }
    
    const Page = () => {
      const lambPrice = 1234.567;
      const jetPrice = 999999.99;
      const bootPrice = 34.567;
      
      return (
        <div>
          <p>One lamb is <span className="expensive">{numberToPrice(lambPrice)}</span></p>
          <p>One jet is {numberToPrice(jetPrice,{ showDecimals: false })}</p>
          <p>Those gumboots will set ya back {numberToPrice(bootPrice,{ showDecimals: false,showSymbol: false })} bucks.</p>
        </div>
      );
    };

    请注意,我不会检查我在上述任何一个有效的数字。那是因为 …

    9. The store is the component’s servant

    我可能写了这么几千次:

    if (props.user.signInStatus === SIGN_IN_STATUSES.SIGNED_IN)..

    (我被告知,我夸张,像,一个gazillion时代。)

    最近我决定,如果我做这样的检查,我做错了。我想只问“是用户登录?”,而不是“用户登录状态等于登录?”

    的组件在他们的生命周期中所做的已经足够, 他们不应该去担心他们的父组件会传一些什么参数。 比如说Price不用管传入的数据是否是数字。

    你会看到,如果你的store中的数据被设计为与您的组件匹配,您的组件将更加简单。我之前说过,复杂性是bug隐藏的地方。组件中的复杂性越低,bug出现的几率越低。

    但是复杂这个问题肯定存在。

    我的建议是:

    1. 制定你的组件的一般结构和他们需要的数据

    2. 设计您的store以支持这些要求

    3. 做任何你需要做的输入数据,使其适合store。

    对于这最后一点,我建议一个单一的模块,所有的按传入的信息重命名props,将字符串转换为数字,将对象转换为数组,将日期字符串转换为日期对象。

    如果你正在进行一个react/redux, 你可以在一个动作创建者中获取搜索结果

    fetch(`/api/search?${queryParams}`)
    .then(response => response.json())
    .then(normalizeSearchResultsApiData) // the do-it-all data massager
    .then(normalData => {
        // dispatch normalData to the store here
    });

    你的组件将会感谢你的。

    10. Importing components without relative paths

    不这样做的话后患无穷啊!

    import Button from '../../../../Button/Button.jsx';
    import Icon from '../../../../Icon/Icon.jsx';
    import Footer from '../../Footer/Footer.jsx';

    或者你可以这样做

    import {Button,Icon,Footer} from 'Components';

    理论上你可以:

    @H_403_56@
  • 在导出每个组件的地方创建单个index.js

  • 使用Webpack的resolve.alias将组件重定向到该索引文件

  • 但是当我写的代码我来认识到这是一个坏主意,有三个原因:当我写代码的时候, 我才认识到上面的模式并不好,原因有三个。

    1. 在Webpack2 似乎改变了原有的API。

    2. eslint将会检测到错误, 由于找不到你引用的组件(因为resolve.alias)。

    3. 如果你使用一个好的IDE,它会知道你的组件在哪里。你会得到关于不提供所需props的提示, 也无法通过Command+click 打开文件这个功能。如果你这样做,你的IDE将不再知道在哪里找到该组件,你会失去这些给力的功能

    Wrap up

    这就是全部, 我非常确定我将在今年看到这些模式的应用。 或许你们今天就会使用它。 你也可以分享一些你觉得不错的模式。

    喔, 我决定我不关心你是否点击了绿色的心。

    I WILL NOT BE DEFINED BY AN INTERNET METRIC.

    猜你在找的React相关文章