У вас есть компоненты React. Вы выяснили, как использовать Redux для передачи им данных. Вы создаете компонент, который создает / обновляет / удаляет элемент в вашем API, и помещаете его на страницу, отображающую элементы из вашего API. Как сделать так, чтобы вся страница перезагружалась, чтобы отображались обновленные данные каждый раз, когда пользователь вносит изменения? Состояние Redux и массив зависимостей useEffect()
.
Можно использовать только React для передачи данных из нашего счетчика в родительский элемент, чтобы получить почти такой же эффект счетчика в моем примере, но использование состояния Redux и массива зависимостей React useEffect()
в некоторых отношениях проще и применимо ко многим другим ситуации.
Эта статья предполагает базовое знакомство с API, React, Redux и Node. Я запустил это приложение с create-react-app
и использую функциональные компоненты и хуки. Вы можете найти весь код в этом репозитории. Я работаю с React (v17.0.1), Redux (v4.0.5), react-redux (v7.2.2) и redux-thunk (v2.3.0). В этом примере я использовал Dog API, чтобы отображать случайные изображения шиба-ину.
После тестирования конечных точек API (в данном случае в браузере с JSONView) я начал с настройки типов действий и создателей действий для запроса GET Dog API. Я могу создавать асинхронные действия благодаря redux-thunk. В этом небольшом примере я оставил свои запросы на выборку внутри создателей действий.
Ниже приведены результаты создателя действий с действием загрузки, которые будут важны позже.
> shibaActions.js function shibesRequested() { return { type: shibaConstants.SHIBES_REQUESTED } } function success(result) { return { type: shibaConstants.SHIBES_FETCHED, payload: result } } function failure(error) { return { type: shibaConstants.SHIBE_FETCH_FAIL, payload: error } }
Далее редуктор:
> shibaReducer.js const reducer = (state = initialState, action) => { switch(action.type) { case shibaConstants.SHIBES_REQUESTED: return { ...state, shibasLoading: true, shibasFetched: false } case shibaConstants.SHIBES_FETCHED: return { ...state, shibasLoading: false, shibasFetched: true, shibas: action.payload } case shibaConstants.SHIBE_FETCH_FAIL: return { ...state, shibasLoading: false, shibasFetched: false } default: return state } }
Наконец, я инициализирую магазин. Первый элемент в composeEnhancers включает Redux Dev Tools, и эта настройка позволяет использовать как инструменты разработчика, так и промежуточное ПО (в данном случае redux-thunk).
> index.js import { compose, createStore, applyMiddleware } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk'; import reducer from './store/shibaReducer'; const rootReducer = reducer; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk))); ReactDOM.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode>, document.getElementById('root') );
Перейдем к нашим компонентам! Я настроил App.js для отображения изображений Shiba на основе числа в местном штате. Если бы это было реальное приложение, я бы потратил время на создание уникального ключа и замещающего текста для каждого изображения.
> App.js import { connect } from 'react-redux'; import { shibaActions } from './store/shibaActions'; import Counter from './components/Counter'; function App(props) { const [number, setNumber] = useState(1); useEffect(() => { props.fetchShibes(number) }, []); if (!props.shibes || !props.shibes[0]) { return (<h1 className="heading">Loading!</h1>) } else { let shibaImages = props.shibes.map(shiba => { return ( <img className="image" src={shiba} alt="shiba" key={shiba}> </img> ); }) return ( <div className="App"> {shibaImages} <Counter /> </div> ); } } const mapStateToProps = (state) => { return { shibes: state.shibas } } const mapDispatchToProps = (dispatch) => { return { fetchShibes: (num) => dispatch(shibaActions.fetchShibes(num)) } } export default connect(mapStateToProps, mapDispatchToProps)(App);
Пустой useEffect()
массив зависимостей (скобки после функции) означает, что страница не будет повторно визуализироваться бесконечно, и повторная визуализация не будет инициирована обновлениями зависимостей компонента. Примечание. Денни Скотт и команда React не рекомендуют пустые массивы зависимостей, поскольку они скрывают ошибки.
Теперь, когда мои шибки отображаются, я запускаю компонент счетчика. Поскольку этот API не имеет конечных точек POST, UPDATE или DELETE, счетчик изменит значение num
, переданное на URL-адрес запроса GET в создателе действия.
let url = `https://dog.ceo/api/breed/shiba/images/random/${num}`;
Сначала я обновляю свои типы действий, создателей действий и редуктор. В этом небольшом примере я использую один файл для каждого из них, но обычно я использую несколько редукторов и использую combineReducers()
hook.
> shibaActions.js function addOne(num) { return dispatch => { let number = num + 1; dispatch(add(number)) } function add(number) { return { type: shibaConstants.ADD_ONE, payload: number } } } function subOne(num) { return dispatch => { let number = num - 1; dispatch(sub(number)) } function sub(number) { return { type: shibaConstants.SUB_ONE, payload: number } } } > shibaReducer.js case shibaConstants.ADD_ONE: return { ...state, counter: action.payload } case shibaConstants.SUB_ONE: return { ...state, counter: action.payload }
Еще добавляю счетчик в начальное состояние, чтобы всегда было 1 шибе.
> shibaReducer.js const initialState = {shibasLoading: false, shibasFetched: false, counter: 1};
Теперь о самом счетчике - довольно просто.
> Counter.js import React from 'react'; import { connect } from 'react-redux'; import { shibaActions } from '../store/shibaActions'; function Counter(props) { const handleOnAdd = () => { props.addOne(props.counter) } const handleOnSub = () => { props.subOne(props.counter) } return ( <div className="container-counter"> <h1 className="heading">Counter!</h1> <button onClick={handleOnAdd}>+</button> <p>{props.counter}</p> <button onClick={handleOnSub}>-</button> </div> ) } const mapStateToProps = (state) => { return { counter: state.counter } } const mapDispatchToProps = (dispatch) => { return { addOne: (num) => dispatch(shibaActions.addOne(num)), subOne: (num) => dispatch(shibaActions.subOne(num)) } } export default connect(mapStateToProps, mapDispatchToProps)(Counter);
Затем, чтобы наша страница перезагружалась каждый раз при нажатии кнопки изменения счетчика, мы должны вернуться к App.js. Мы будем использовать mapStateToProps()
для доступа к счетчику в компоненте страницы и передавать его вместо локального состояния number
при отправке fetchShibes()
.
> App.js // const [number, setNumber] = useState(1); useEffect(() => { props.fetchShibes(props.counter) }, [props.counter]);
Как вы можете видеть выше, единственное, что нам нужно сделать, чтобы перезагрузить страницу с изменениями кнопок, - это поместить состояние счетчика в useEffect()
скобки зависимостей (команда React рекомендует назначать состояние переменным вместо использования props.state внутри массив зависимостей). Добавьте немного стиля, и у нас есть базовый маленький счетчик Шиба.
Перезагрузить страницу с помощью асинхронного действия лишь немного сложнее - вы должны использовать состояние загрузки, подобное shibasLoading
выше. Если вы используете только состояние конечного результата, например shibasFetched
, то страница будет перезагружена только при первом успешном запросе. Поэтому, если у вас есть пользователь, добавляющий несколько вещей в ваш API, он покажет только первое.
Эта концепция также позволяет создавать сообщения об ошибках в вашем приложении. После добавления объектов состояния shibasFetched
и shibasLoading
в mapStateToProps()
я обновил свой файл App.js.
> App.js if (!props.shibes || !props.shibes[0]) { return ( <div> {(props.shibasLoading || !props.shibasFetched) && <h1 className="heading">Loading!</h1>} {!props.shibasLoading && !props.shibasFetched && <h1>Something went wrong - shibas not loaded.</h1>} </div> ) } else { let shibaImages = props.shibes.map(shiba => { return ( <img className="image" src={shiba} alt="shiba" key={shiba}> </img> ); }) return ( <div className="App"> {!props.shibasLoading && !props.shibasFetched && <h2>Something went wrong - shibas not loaded.</h2>} <Counter /> {shibaImages} </div> ); }
Используя условную логику JSX и наше состояние Redux, мы можем отображать различные сообщения об ошибках в зависимости от состояния наших асинхронных запросов.
Заключение
Пытаясь понять это сам, я попытался создать простой пример того, как использовать ловушку useEffect()
для перезагрузки компонентов на основе изменений, вносимых другими компонентами.
Есть много способов использовать это в приложении, и сама базовая концепция позволяет делать забавные вещи, такие как настраиваемые сообщения об ошибках, с использованием условной логики JSX.
Если это помогло вам, когда вы застряли или оставили вопрос, оставьте мне комментарий!