Шаблоны проектирования React и лучшие практики

React.js
Шаблоны проектирования React и лучшие практики

Эту статью прочитал Микеле Бертоли«Шаблоны проектирования React и лучшие практики»Заметки о чтении книги, пожалуйста, нажмите, чтобы поддержать автораздесьКупить.


Talk is cheap, just show me the code.

Можно много говорить о чепухе, и я перейду непосредственно к галантерее.

Об условном решении в функции рендеринга

В React есть ситуация, когда нам часто нужно решить, следует ли отображать определенные компоненты на основе условных суждений. Так:

<div>
    { isLoggedIn ? <LogoutButton /> : <LoginButton /> }
    { visible && <Modal /> }
</div>

Когда условное суждение становится более сложным, мы можем использовать методы и вычисляемые свойства, чтобы заменить троичную операцию и или или суждение.

handleShowLoginButton() {
    return this.isLoggedIn && this.isAuthed;
}
get getVisible() {
    return this.visible && this.displayMode === "normal"
}

render() {
    return (<div>
        { handleShowLoginButton() ? <LogoutButton /> : <LoginButton /> }
        { getVisible && <Modal /> }
    </div>)
}

Затем приходит черная технология, когда мы хотим извлечь эту логику суждения из функции рендеринга рендеринга, чтобы функция рендеринга отвечала только за рендеринг. нам нужно использоватьrender-if render-only-if jsx-control-statementsЭти помощники зависят от него. Пожалуйста, посмотри:

const isShowLoginButton = renderIf(
    this.isLoggedIn && this.isAuthed
)
return (<div>
    { isShowLoginButton(<LoginButton />) } {/* 完了结果 LogoutButton 我还需要另外写一个 isShowLogoutButton 的 renderIf 去判断显示与否吗 */}
</div>)

Тогда render-only-if по сути является функцией более высокого порядка, которая более элегантна по форме, чем render-if .

const LoginButtonOnlyIf = onlyIf(
    ({ isLoggedIn && isAuthed }) => {
        return isLoggedIn && isAuthed
    }
)(LoginButton)

return (
    <LoginButtonOnlyIf 
        isLoggedIn={isLoggedIn}
        isAuthed={isAuthed}
    />
)

Суммировать:

  • Если это просто условное суждение, троичная сумма и/или оператор удовлетворили потребности большинства людей;
  • Если вы хотите разделить проблемы, renderIf — хорошая идея;
  • Наконец, если вы хотите, чтобы ваша логика оценки использовалась повторно (как если несколько страниц и несколько компонентов должны использовать логику для оценки состояния входа и разрешений пользователя), вы можете использовать onlyIf для создания повторно используемых компонентов более высокого порядка в проекте.

Затем мы, наконец, рассмотрим использование этой отвратительной штуки jsx-control-statements:

<If condition={this.handleShowLoginButton}>
    <LoginButton />
</If>

<When condition={this.handleShowLoginButton}>
    <LoginButton /> 
</When>
<When condition={!this.handleShowLoginButton}>
    <LogoutButton /> // => 好了这下终于看见我们的 LogoutButton 出现了
</When>
<Otherwise>
    <p>oops.. no condition matched.</p>
</Otherwise>


<ul>
    <For each="resultItem" of={this.resultList}>
        <li>{resultItem.name}</li>
    </For> 
    // => {resultList.map(resultItem => <li>{resultItem.name}</li>)}
</ul>

Разработка повторно используемых компонентов

Это касается производительности и ремонтопригодности.

Всегда имейте в виду, что состояние установки запускает повторную визуализацию компонента. Поэтому в состоянии должны храниться только те значения, которые используются методом рендеринга.

Ниже приведены Дэн Абрамов (я не знаю, кто он), чтобы создать шаг, чтобы помочь нам сделать правильный выбор статуса:

function shouldIKeepSomethingInReactState() {
    if (canICalculateItFromProps()) {
        // 不要把 props 属性直接在 state 状态中使用,
        // 应该直接在 render() 函数里计算使用它们
        return false
    }
    if (!amIUsingItInRenderMethod()) {
        // 不要把没有参与渲染的数据放进 state 状态里,
        // 换句话说就是只有需要涉及到组件 render 渲染更新的数据才放到 state 里
        return false
    }
    // 除了以上情况,都可以使用状态。
    return true;
}

Что касается проверки типа реквизита, React предоставляетpropTypesсвойства, которые мы можем использовать:

const Button = ({text}) => <button>{text}</button>

Button.propTypes = {
    text: React.PropTypes.string
}

Но на самом деле в мире TypeScript мы можем напрямую объявить интерфейс свойства prop для нашего компонента React в виде класса шаблона:

interface IButtonProps = {
    text: string;
}

class ButtonClass extend React.Component<IButtonProps, IButtonStates> {}
// => 顺带连 state 属性检验也可以加进来

Далее автоматически сгенерируйте документацию для компонента, используяreact-docgenэтот инструмент.

import React from 'react';

/**
 * Sheet 组件
 */
const Sheet = ({title}) => <div>{title}</div>

Sheet.prototype = {
    /**
     * Sheet 标题
     */
    title: React.PropTypes.string
}

бегатьreact-docgen Sheet.jsРезультатом является следующее описание json:

{
    "description": "Sheet 组件",
    "displayName": "Sheet",
    "methods": [],
    "props": {
        "title": {
            "type": {
                "name": "string"
            },
            "required": false,
            "description": "Sheet 标题"
        }
    }
}

Используя этот json-файл в качестве входных данных для проекта документации переднего плана команды, вы можете автоматически генерировать полезные инструкции по документации компонентов.

Хорошо, давайте выявим самого большого убийцу в отрасли.storybook.

storybook

npm i --save @kadira/react-storybook-addon

(Кажется, что @kadira/react-storybook-addon был утилизирован. Согласно документу рекомендуется написать собственный сборник рассказов на официальном сайте)

Документы рассказа помещаются вstories, мы создаем лист.js в папке историй, чтобы определить документ истории компонентов, которые мы определили выше.

// => stories/sheet.js
import React from 'react';
import Sheet from '../src/components/Sheet';
import { storiesOf } from '@kadira/storybook'

storiesOf('Sheet', module)
    .add('没有 title 属性的 Sheet 的故事..', () => (
        <Sheet/>
    ))

Но мы должны написать историю, а также сначала настроить Storybook в корневом каталоге:

// => .storybook/config.js => 根目录下创建 .storybook 文件夹
import { configure } from '@kadira/storybook';

function loadStories() {
    require('../src/stories/sheet')
}

configure(loadStories, module)

Наконец, мы добавляем скрипт в наш package.json для запуска нашей истории.

    "storybook": "start-storybook -p 9001"

После запуска вы можете увидеть файл истории на порту 9001.

react-official storybook


Рассмотрим детальнее компонент

Что касается компонентов-контейнеров и компонентов-дураков, я не буду о них здесь говорить. Ведь это первичный контент, поэтому считаться с ним непросто. Давайте сразу к делу.

Например, в самом простом случае реализуйте компонент более высокого порядка, который добавляет к компоненту имя класса:

const withClassName = Component => props => (
    <Component {...props} className="my-class" />
)

Вышеупомянутое свойство — это просто перенесенная опора.На самом деле мы можем переместить все остальные свойства компонента, кроме опоры.

const withTimer = Component => (
    class extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                timer: null
            }
        }
        componentDidMount() {
            this.timer = setTimeInterval(() => {
                console.log('每个1.5s打印一次日志哈哈哈')
            }, 1500)
        }
        componentWillUnmount() {
            clearInterval(this.timer)
        }
        render() {
            // => 原封不动把接收到的 props 传给 Component
            //    state 传入 Compnent 其实是可选项,根据实际需求决定
            return <Component {...this.props} {...this.state} />
        }
    }
)

// => 然后我们就可以给普通的组件加上定时打印日志的功能啦
const SheetWithTimer = withTimer(Sheet);

потомrecomposeЭта библиотека уже предоставила нам некоторые высокоуровневые компоненты для практических сценариев.

recompose

Далее, давайте сделаем несколько трюков, см.функциональный подкомпонентКак играть. Прежде всего, наш withClassName выше явно слишком мал, и className записано до смерти! ? Не пишите все до смерти, вам нужно изменить это через несколько минут.

还改不改需求

Очевидно, нам нужно сделать еще один уровень логики в компоненте withClassName, а затем динамически передать className дочернему компоненту после решения. На этот раз, чтобы сделать трюк, мы решили использовать режим подкомпонента функции.

const withClassName = ({children}) => children('my-class'); // => wft,这里 'my-class' 还不照样是写死的...

<withClassName>
    {(classname) => <Component className={classname} />}
</withClassName>

Затем мы видим бесконечные возможности... Хотя withClassName по-прежнему является компонентом без состояния, мы можем добавить к нему жизненные крючки, методы функций и состояние, как и компонент withTimer. Тогда в рендере разница (мы не используем напрямую, а выполняем child()):


render() {
    return <Component {...props} /> // => 一般做法

    renturn {children(props)} // => 函数子组件做法
}

Возможно, более подходящим примером является сценарий, в котором компоненту более высокого порядка необходимо сделать HTTP-запрос, а данные, возвращенные из запроса, передаются дочернему компоненту для рендеринга.

<Fetch url="...">
    {data => <MyComp data={data} />}
</Fetch>

Наконец, давайте поговорим о CSS

Прежде всего, просто и грубо, мы можем написать стиль прямо в элементе html, который выглядит в мире jsx так:

<div style={{ fonSize: this.state.fontSize }} />

Тогда встроенный стиль стиля имеет недостаток, заключающийся в том, что вы не можете написать его напрямую.媒体查询а также伪类伪元素,Конечно动画(Небольшие межстраничные объявления: используйте библиотеку реагирования на анимацию.react-motionВы не можете написать это.

такRadiumвозник.

С Radium вы можете сделать это произвольно:

import radium from 'radium'

const myStyle = {
    fontSize: '12px',
    ':hover': {
        color: '#abc'
    },
    '@media (min-width: 720px)': {
        color: '#121212'
    }
}

const Div = () => <div style={myStyle} />

export default radium(Div);

Конечно, чтобы использовать медиа-запросы, вам также необходимо сохранить элемент styleroot на самом внешнем уровне, иначе Radium не будет знать, где находится корневой элемент вашего медиа-запроса.

import { StyleRoot } from 'radium'

class App extends Component {
    render() {
        return (
            <StyleRoot>
                <router-view />
            </StyleRoot>
        )
    }
}

Конечно, мы можем совершенно не использовать встроенные стили, а использовать css-модули на основе className.

import styles from './index.less'

render() {
    return {
        <div className={styles.myDiv} />
    }
}
/* index.less */
.myDiv {
    font-size: 12px
}

/* 默认模块会生成一堆我们看不懂的英文类名,如果想要让类名不作用在局部而是全局,可以使用 :global */
:global .myGlobalDiv {
    font-size: 15px
}

/* 我们还可以使用 composes 把其他类的样式混进来 */
.myDivCop {
    composes: .myDiv;
    color: '#101010'
}

Кроме того, если ваше className не хочет писать форму style.[имя класса], но хочет написать форму строкового имени класса напрямую, вы можете позаимствоватьreact-css-modulesэта библиотека.

Затем используется эта поза:

import cssModules from 'react-css-modules'
import styles from './index.less'

class DivComp extends Component {
    render() {
        return (
            <div className='myDiv' />
        )
    }
}

export cssModules(DivComp, styles)

Затем есть еще один вызов, который может стать тенденцией в будущем.styled-componentsПарень, потому что арендодатель действительно не может учиться, поэтому я не буду говорить об этом здесь.


Отсканируйте код, чтобы получить вознаграждение, и сообщите мне тему контента, который вы хотите прочитать в следующем выпуске (милое лицо)

Шучу, добро пожаловать, чтобы оставить сообщение в области комментариев и сообщить мне тему контента, которую вы хотите прочитать,Я выберу только то, что хочу, и я тоже не выберу хахахаЯ постараюсь найти время для демонстраций и расширенного чтения. Вы также можете обратить внимание и призвать меня обновить статью в следующем выпуске.

黄大发的赞赏码