Render Props and Hooks

React.js

            Render Props and Hooks

Каталог статей:

  • Что такое Render Props
  • Применение Render Props
  • Что такое React-хуки
  • Применение хуков React
  • Суммировать

Что такое Render Props

Короче говоря, пока значение свойства в компоненте является функцией, можно сказать, что компонент использует Render Props. Похоже, это так. Итак, каковы сценарии применения Render Props? Давайте начнем с простого примера. Если мы хотим реализовать компонент приветствия, это может быть реализовано в начале следующим образом:

const Greeting = props => (
    <div>
        <h1>{props.text}</h1>
    </div>
);

// 然后这么使用
<Greeting text="Hello 🌰!" />

Но если вам также нужно отправить выражение при приветствии, то это может быть реализовано так:

const Greeting = props => (
    <div>
        <h1>{props.text}</h1>
        <p>{props.emoji}</p>
    </div>
);
// how to use
<Greeting text="Hello 🌰!" emoji="😳" />

Затем, если вы хотите добавить ссылку, вам нужноGreetingЛогика отправки ссылок реализована внутри компонента, что явно нарушает один из шести принципов разработки ПО.принцип открыто-закрыто, то есть каждую модификацию нужно модифицировать внутри компонента.

Принцип открыт-закрыт: закрыт для модификации, открыт для расширения.

Есть ли способ избежать такой модификации?Конечно, есть, о чем мы поговорим далее.Render Props, но перед этим рассмотрим очень простую функцию суммирования:

const sumOf = array => {
    const sum = array.reduce((prev, current) => {
        prev += current;
        return prev;
    }, 0);
    console.log(sum);
}

Функция этой функции очень проста, просуммируйте массив и распечатайте его. Но если вам нужно поставитьsumпройти черезalertПокажи, он снова появится?sumOfЧтобы изменить его внутренне, и вышеGreetingАналогично, да, у этих двух функций одна и та же проблема, то есть при изменении требований их обе нужно модифицировать внутри функции.

Что касается второй функции, вы, возможно, скоро сможете понять, как ее использовать.Перезвонитерешать:

const sumOf = (array, done) => {
    const sum = array.reduce((prev, current) => {
        prev += current;
        return prev;
    }, 0);
    done(sum);
}

sumOf([1, 2, 3], sum => {
    console.log(sum);
    // or
    alert(sum);
})

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

В отличие от компонентов ReactGreeting, решить возникшие ранее проблемы, по сути, иsumOfФункция обратного вызова аналогична:

const Greeting = props => {
    return props.render(props);
};
// how to use
<Greeting
    text="Hello 🌰!"
    emoji="😳"
    link="link here"
    render={(props) => (
    <div>
        <h1>{props.text}</h1>
        <p>{props.emoji}</p>
        <a href={props.link}></a>
    </div>
)}></Greeting>

аналогия с предыдущимsumOfЭто очень похоже, только волосок такой же:

  • sumOfвыполнив функцию обратного вызова вdoneи положиsumв него, покаsumOfПередайте функцию во втором параметре функции, чтобы получитьsumзначение, а затем напишите индивидуальные требования
  • Greetingвыполнив функцию обратного вызова вprops.renderи положиpropsв него, покаGreetingкомпонентrenderВы можете получить функцию, передав функциюpropsзначение и вернуть желаемый пользовательский интерфейс

Стоит отметить, что не толькоrenderРеквизиты рендеринга можно вызывать только путем передачи функции в атрибут. На самом деле любой атрибут может называться реквизитами рендеринга, если его значение является функцией. Например, в приведенном выше примереrenderимя свойства изменено наchildrenЭто на самом деле проще в использовании:

const Greeting = props => {
    return props.children(props);
};
// how to use
<Greeting text="Hello 🌰!" emoji="😳" link="link here">
{(props) => (
    <div>
        <h1>{props.text}</h1>
        <p>{props.emoji}</p>
        <a href={props.link}></a>
    </div>
)}
</Greeting>

Таким образом, вы можете напрямуюGreetingФункция написана в теге, по сравнению с предыдущейrenderболее интуитивным.

так,Render Props в React Вы можете думать об этом как о функции обратного вызова в JavaScript..

Применение Render Props

Вышеизложенное кратко описывает, что такое Render Props, а затем каково практическое применение Render Props в реальной разработке?компоненты более высокого порядкаРешаемые задачи аналогичны, как дляРешить проблему повторного использования кода.

Если вы не знакомы с высокоуровневыми компонентами, то можете прочитать, что автор писал ранееКомпоненты высшего порядка в React и сценарии их применения.

Компонент, который просто реализует функцию «переключателя»:

class Switch extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            on: props.initialState || false,
        };
    }
    toggle() {
        this.setState({
            on: !this.state.on,
        });
    }
    render() {
        return (
            <div>{this.props.children({
                on,
                toggle: this.toggle,
            })}</div>
        );
    }
}
// how to use
const App = () => (
    <Switch initialState={false}>{({on, toggle}) => {
        <Button onClick={toggle}>Show Modal</Button>
        <Modal visible={on} onSure={toggle}></Modal>
    }}</Switch>
);

это простоПовторное использование отображения и логики скрытых модальных всплывающих оконкомпоненты, такие как отображениеOtherModalпросто заменитьModalВот и все, чтобы достичь цели повторного использования логического кода «переключателя».

Render Props больше похож наИнверсия управления (IoC), он отвечает только за определение интерфейса или данных и передачу их вам через параметры функции, как использовать эти интерфейсы или данные, полностью зависит от вас.

Если вы не знакомы с Inversion of Control, вы можете прочитать то, что автор написал ранее.Философия IoC во внешнем интерфейсе

Render Props VS HOC

Проблемы, решаемые Render Props икомпоненты более высокого порядкаРешаемые задачи аналогичны, как дляРешить проблему повторного использования кода, то в чем между ними разница, кратко разберем их соответствующие характеристики:

HOC

недостаток:

  • Поскольку компоненты более высокого порядка могут быть вложены несколько раз, и нам трудно гарантировать, что имена свойств в каждом компоненте более высокого порядка будут разными, поэтомуСвойства легко переопределяются.
  • При использовании компонентов более высокого порядка компоненты более высокого порядка эквивалентнычерный ящик, мы должны посмотреть, как его реализовать, чтобы использовать:

преимущество:

  • можно использоватьcomposeметод для объединения нескольких компонентов более высокого порядка, а затем использовать
// 不要这么使用
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent));
// 可以使用一个 compose 函数组合这些高阶组件
// lodash, redux, ramda 等第三方库都提供了类似 `compose` 功能的函数
const enhance = compose(withRouter, connect(commentSelector));
const EnhancedComponent = enhance(WrappedComponent);
  • Удобство вызова (ES6 + синтаксис декоратора)
@withData   
class App extends React.Component {}

Render Props

  • недостаток
    • Слишком глубокая вложенность также можетад обратный вызов
  • преимущество
    • Устраняет недостатки HOC

Render Props и HOC не являются отношениями «или-или».Поняв их соответствующие преимущества и недостатки, мы можем реализовать их подходящим образом в подходящих сценариях.

Что такое React-хуки

React Hooks — это новая функция, представленная в React 16.8, которая позволяет функциональным компонентам иметь тот же (похожий) «жизненный цикл», что и компоненты в стиле классов, чтобы лучше использовать функции React в функциональных компонентах.

Цель введения командой React хуков и вышеупомянутогокомпоненты более высокого порядка,Render PropsТо же самое верно и для повторного использования кода.

Прежде чем разбираться в React Hooks, давайте рассмотрим предыдущие Render Props.SwitchПример для сравнения:

class Switch extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            on: props.initialState || false,
        };
    }
    toggle() {
        this.setState({
            on: !this.state.on,
        });
    }
    render() {
        return (
            <div>{this.props.children({
                on,
                toggle: this.toggle,
            })}</div>
        );
    }
}
// how to use
const App = () => (
    <Switch initialState={false}>{({on, toggle}) => {
        <Button onClick={toggle}>Show Modal</Button>
        <Modal visible={on} onSure={toggle}></Modal>
    }}</Switch>
);
// use hooks
const App = () => {
    const [on, setOn] = useState(false);
    return (
        <div>
            <Button onClick={() => setOn(true)}>Show Modal</Button>
            <Modal visible={on} onSure={() => setOn(false)}></Modal>
        </div>
    );
}

Для сравнения мы можем легко обнаружить, что версия Hooks использует только несколько простых API (useState, setOn, on) готовоSwitchКомпонент класса требует 20 строк кода, что значительно сокращает объем кода.

Сокращение кода — лишь одно из преимуществ хуков, но их более важная цель —Мультиплексирование логики состояний. (То же самое верно для высокоуровневых компонентов и Render Props. Повторное использование логики состояния на самом деле является более конкретным утверждением повторного использования кода)

Несколько преимуществ хуков:

  • Хотя назначение хуков такое же, как у компонентов более высокого порядка и реквизитов рендеринга, оно предназначено для повторного использования кода, но компоненты хуков более высокого порядка и реквизиты рендеринга проще и понятнее и не вызовут ад вложенности.
  • Более простое разделение пользовательского интерфейса и состояния
  • Хук может ссылаться на другие хуки
  • Решите болевые точки компонентов класса
    • thisподвержен ошибкам
    • Логика, разделенная на разные циклы объявлений, затрудняет понимание и поддержку кода.
    • Высокая стоимость повторного использования кода (компоненты более высокого порядка могут легко увеличить объем кода)

Применение хуков React

React официально предоставляет следующие часто используемые хуки:

  • Основные крючки:useState,useEffect,useContext
  • Дополнительные крючки:useReducer,useCallback,useMemo,useRef,useImperativeHandle,useLayoutEffect,useDebugValue

useEffect

useEffectКрючки делают именно то, что следует из названия - для обработки, например.подписка,сбор информации,Манипуляции с DOMДождитесь побочных эффектов. его функция иcomponentDidMount, componentDidUpdateа такжеcomponentWillUnmountЭти функции жизненного цикла аналогичны.

Например, мы хотим отслеживать поле вводаinputввод, сuseEffectМы можем сделать это так:

function Input() {
    const [text, setText] = useState('')
    function onChange(event) {
        setText(event.target.value)
    }
    useEffect(() => {
        // 类似于 componentDidMount 和 componentDidUpdate 两个生命周期函数
        const input = document.querySelector('input')
        input.addEventListener('change', onChange);
        return () => {
            // 类似于 componentWillUnmount
            input.removeEventListener('change', onChange);
        }
    })
    return (
        <div>
            <input onInput={onChange} />
            <p>{text}</p>
        </div>    
    )
}

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

Если входящийuseEffectфункция метода возвращает функцию, котораяфункция, которая возвращаетОн будет вызван, когда компонент будет удален. Здесь мы можем выполнить некоторые операции по очистке, такие как очистка timerID или отмена ранее опубликованной подписки. Может быть более интуитивно понятно написать следующее:

useEffect(function didUpdate() {
    // do something effects
    return function unmount() {
         // cleaning up effects
    }
})

когдаuseEffectКогда передается только один параметр,каждый разrenderбудет выполнен позжеuseEffectфункция:

useEffect(() => {
    // render 一次,执行一次
    console.log('useEffect');
})

когдаuseEffectКогда второй переданный параметр является массивом,Входящая функция обратного вызова будет выполняться только при изменении значения массива (зависимости), например:

Хотя алгоритм сравнения React обновляет измененные части только при рендеринге DOM, он не распознаетuseEffectвнутренние изменения, поэтому разработчик должен сообщить React, какие внешние переменные использовать, через второй параметр.

useEffect(() => {
    document.title = title
}, [title])

потому чтоuseEffectОбратный вызов внутренне использует внешнийtitleпеременная, поэтому при необходимости, только еслиtitleЕсли обратный вызов выполняется только при изменении значения, вам нужно только передать массив во втором параметре и записать в массив внутренние зависимые переменные.titleЕсли значение изменится,useEffectВнутри обратного вызова вы можете определить, должен ли обратный вызов выполняться через входящие зависимости.

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

useEffect(() => {
    // 只会在首次 render 之后执行一次
    console.log('useEffect')
}, [])

useContext

В Реакте естьcontextконцепция, которая позволяет намДелитесь состоянием между компонентами без передачиpropsслой за слоем, простой пример:

Редукс использует ReactcontextФункции позволяют обмениваться данными между компонентами.

const ThemeContext = React.createContext();
function App() {
    const theme = {
        mode: 'dark',
        backgroundColor: '#333',
    }
    return (
        <ThemeContext.Provider value={theme}>
            <Display />
        </ThemeContext.Provider>
    )
}

function Display() {
    return (
        <ThemeContext.Consumer>
            {({backgroundColor}) => <div style={{backgroundColor}}>Hello Hooks.</div>}
        </ThemeContext.Consumer>
    )
}

НижеuseContextВерсия:

function Display() {
    const { backgroundColor } = useContext(ThemeContext);
    return (<div style={{backgroundColor}}>Hello Hooks.</div>)
}

Вложенная версияConsumer:

function Header() {
    return (
        <CurrentUser.Consumer>
            {user =>
                <Notifications.Consumer>
                    {notifications =>
                        <header>
                            Hello {user.name}!
                            You have {notifications.length} notifications.
                        </header>
                    }
                </Notifications.Consumer>
            }
        </CurrentUser.Consumer>
    );
}

использоватьuseContextПлоский:

function Header() {
    const user = useContext(CurrentUser)
    const notifications = useContext(Notifications)
    return (
        <header>
            Hello {user.name}!
            You have {notifications.length} notifications.
        </header>
    )
}

эмм... Этот эффект чем-то похож на использованиеasyncа такжеawaitЧувство обратного вызова доработанного ада.

Это основные хуки React. Для других официально предоставляемых хуков, если вы заинтересованы, рекомендуется прочитать документацию напрямую, в этой статье мы не будем знакомить их по одному.

На что следует обратить внимание при использовании

Хотя React Hooks приносят нам удобство, мы также должны следовать некоторым их соглашениям, иначе эффект станет только контрпродуктивным:

  • избегать вЦиклы, условные суждения, вложенные функциивызыватьHooks, чтобы обеспечить стабильность вызывающей последовательности;
  • Толькокомпонент определения функцииа такжеHooksможно назватьHooks, избегатькомпонент классаилиОбычная функциявызывать;
  • не может быть вuseEffectиспользуется вuseState, React сообщит об ошибке;
  • Компоненты класса не будут заменяться или отбрасываться, нет необходимости принудительно преобразовывать компоненты класса, и эти два метода могут сосуществовать.

Суммировать

Основная идея Render Props состоит в том, чтобы вызыватьthis.props.children()(Разумеется, это может быть любое другое значение, являющееся именем атрибута функции) Передайте некоторые состояния внутри компонента (обратитесь к callback-функции), а затем получите внутреннее состояние компонента через параметры функции в функции соответствующий атрибут вне компонента, а затем использовать функцию в функции Обработать соответствующий пользовательский интерфейс или логику в . Хуки немного похожи на Render Props.литография(обратитесь к предыдущемуuseContext)Каштан.

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

  • Render Props
  • Hooks
  • HOC

При сравнении обнаруживается, чтоHooksПреимущество метода самое большое, и он решает некоторые болевые точки двух других методов, поэтому рекомендуется использовать его.

Связанное чтение:

前端小专栏

Для получения дополнительной галантереи, пожалуйста, обратите внимание на паблик-аккаунт "Front-end small column:QianDuanXiaoZhuanLan"