React 소개, 4부
게시 됨: 2020-08-06마침내 React에 대한 이 작은 소개의 마지막 부분에 도달했습니다. 논리적으로 저는 파이프라인에 많은 것들을 남겨두었기 때문에 앞으로 JS 개발 스택의 다양한 측면에 초점을 맞추게 될 더 많은 게시물을 작성하는 것을 배제하지 않습니다. 하지만 이 마지막 게시물을 통해 여러분은 이미 스스로 성장하고 발전하는 데 필요한 기본 개념.
지난 주에 우리는 매우 흥미로운 시점에서 일을 마무리했습니다. 우리의 목표는 원하는 대로 추가하거나 제거할 수 있는 여러 카운터가 있는 작은 응용 프로그램을 만드는 것이었습니다.

지난 포스트를 마칠 즈음에는 애플리케이션의 상태를 추적할 수 있는 놀라운 저장소가 있었습니다. 이는 UI를 최종적으로 구축하는 데 필요한 모든 기본 사항을 이미 갖추고 있음을 의미합니다.
오늘 우리는 React 구성 요소를 Redux 기반 WordPress 저장소와 연결하여 인터페이스가 저장소의 상태를 표시하고 사용자 상호 작용이 해당 상태를 업데이트하도록 하는 방법을 볼 것입니다.
지난 주 숙제에 대한 해결책
하지만 그렇게 하기 전에 지난 주에 내가 당신에게 남긴 숙제를 빠르게 검토해 봅시다. 본질적으로, 더 이상 사전을 사용하지 않고 객체의 배열을 사용하도록 저장소를 다시 구현하도록 요청했습니다.
const originalStore = { x: 1, y: 2, }; const newStore = [ { id: 'x', value: 1 }, { id: 'y', value: 2 }, ];이 업데이트를 상점에 적용하기 위해 어떤 변경을 하였습니까? 자, 모두 빠르게 검토해 보겠습니다!
행위
상점 작업은 이미 수행한 작업과 정확히 동일하므로 필요하지 않았기 때문에 여기에서 아무 것도 변경하지 않았으면 합니다. 지난 주에 보았듯이 작업은 스토어를 직접 업데이트(또는 액세스)하지 않으므로 스토어의 기본 구조에 대한 변경 사항은 여기에 영향을 미치지 않습니다.
Action은 "업데이트 요청"을 알리는 객체를 생성하는 간단한 함수이며, 이것이 여전히 우리의 actions.js 파일에 있습니다.
export function addCounter( counterId ) { return { type: 'ADD_COUNTER', counterId, }; } export function removeCounter( counterId ) { return { type: 'REMOVE_COUNTER', counterId, }; } export function setCounterValue( counterId, value ) { return { type: 'SET_COUNTER_VALUE', counterId, value, }; }감속기
리듀서는 이전 상태와 디스패치된 작업을 기반으로 상태를 업데이트하는 기능입니다. 이 경우 애플리케이션의 상태를 저장하는 데이터 구조를 변경하고 싶기 때문에 리듀서를 다시 구현해야 합니다.
import { map, find, without } from 'lodash'; export default function reducer( state = [], action ) { switch ( action.type ) { case 'ADD_COUNTER': return [ ...state, { id: action.counterId, value: 0 }, ]; case 'REMOVE_COUNTER': return without( state, find( state, { id: action.counterId } ) ); case 'SET_COUNTER_VALUE': return map( state, ( counter ) => action.id === action.counterId ? { ...counter, value: action.value } : counter ); } return state; } 상상할 수 있듯이, 여기서 우리가 해야 할 일은 상태의 기본값을 빈 객체 {} 에서 빈 배열 [] 로 변경하는 것뿐이었습니다. 그런 다음 작업이 새 저장소를 올바르게 업데이트하도록 switch 블록에서 각 case 를 설정하기만 하면 됩니다. 기본적으로 이것은 배열의 개체를 추가, 제거 또는 업데이트하는 것입니다.
선택기
마지막으로 selectors.js 파일이 있습니다. 지난 주에 보았듯이 선택자는 애플리케이션의 현재 상태와 필요할 수 있는 기타 매개변수를 수신하고 이 상태에서 요청된 값을 검색합니다. 상태가 저장되는 방식을 변경했으므로 선택자의 본문도 변경해야 합니다.
import { map, find } from 'lodash'; export function getCounterIds( state ) { return map( state, 'id' ); } export function getCounterValue( state, counterId ) { const counter = find( state, { id: counterId } ) || {}; return counter.value; }다시 말하지만 큰 문제는 아니지만…
이 예에서는 선택기가 두 개뿐이므로 두 함수만 다시 구현하면 됩니다. 첫 번째는 map 으로 매우 쉽게 얻을 수 있는 모든 카운터의 식별자를 반환하는 역할을 합니다. 두 번째는 지정된 카운터의 값을 반환합니다(또는 지정된 counterId 가 매장에서 찾을 수 없는 경우 undefined ). 따라서 Lodash의 도우미 함수를 사용하여 id 가 counterId 인 카운터를 find 하고 value 을 반환하기만 하면 됩니다. 관련 카운터의 속성(있는 경우).
마지막 메모
내가 당신에게 당신의 상점을 조정하라고 요청한 이유는 당신이 다음과 같은 교훈을 배울 수 있도록 하기 위해서였습니다: 이와 같은 상점을 사용하는 주된 이점은 그들이 블랙박스처럼 행동한다는 것입니다. 당신은 그것이 내부적으로 구성되는 방식을 완전히 변경할 수 있고 모든 것이 예상대로 작동할 것입니다 , 인터페이스(예: 작업 및 선택기)가 변경되지 않는 한.
UI의 구성요소 다시 작성하기
사용자가 원하는 대로 카운터를 추가 및 제거할 수 있기를 바랍니다.

각 카운터를 서로 독립적으로 관리합니다. 2부에서 구현한 UI에 적용된 변경 사항은 무엇입니까? 글쎄, 우리는 분명히 (1) 삭제 버튼을 포함하도록 각 카운터를 수정하고 (2) 새 카운터를 추가하는 새 버튼이 있도록 앱을 조정해야 합니다. 그래서 이것을 해보자!
먼저 src/components/counter.js 파일을 열고 사용자가 버튼을 클릭할 때 카운터가 삭제되도록 새 삭제 버튼과 onDelete prop 을 추가합니다.
const Counter = ( { value, onIncrease, onDecrease, onDelete } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button onClick={ onIncrease }>+</button> <button onClick={ onDecrease }>-</button> <button onClick={ onDelete }>Delete</button> </div> ); export default Counter; 다음으로 (a) 사용자가 추가한 모든 카운터와 (b) 새 카운터를 추가하기 위한 버튼을 렌더링할 새 구성 요소를 만듭니다. 다음과 같이 src/components/counter-list.js 파일을 생성하는 것이 좋습니다.
import Counter from './counter'; const CounterList = ( { addCounter, counterIds } ) => ( <div> { counterIds.map( ( id ) => ( <Counter key={ id } counterId={ id } /> ) ) } <button onClick={ addCounter }>Add Counter</button> </div> ); export default CounterList;이 새로운 구성 요소에는 언급할 가치가 있는 몇 가지 흥미로운 사항이 있습니다.
- 다른 구성 요소(
Counter)를 사용하는 구성 요소(CounterList)입니다. 우리는 튜토리얼에서 이것에 대한 어떤 예도 보지 못했습니다. 그래서... 이제 하나가 생겼습니다! 기억해야 할 유일한 것은 사용하려는 모든 구성 요소를import한다는 것입니다. - 지금까지 사용한 모든 가져오기가 있음에도 불구하고
import문에는 중괄호가 없습니다(import Counter vs import {Counter}). Counter는 이제 기본 내보내기이고 기본 내보내기는 중괄호로 가져오지 않기 때문입니다. - CounterList 구성 요소는 두 가지 속성을 받습니다. addCounter는 새 카운터를 추가할 수 있는 작업이고 counterIds는 앱에 있는 모든 카운터의 식별자가 있는 배열입니다.
- 각 카운터를 렌더링하기 위해 ID 배열을 Counter 인스턴스에 매핑합니다.
- 이 맵에서 Counter 구성요소에는 key와 counterId라는 두 가지 새로운 속성이 있습니다. key는 React가 렌더링 엔진을 최적화하는 데 필요한 소품입니다. counterId는 나중에 논의할 내용입니다.
-
Counter구성 요소에는 예상되는 소품이 포함되어 있지 않습니다. 그것은 우리가 상점을 사용하여 그것들을 채울 것이기 때문입니다(그리고 새로운 propcounterId가 우리에게 도움이 될 것입니다). -
Counter와 마찬가지로CounterList는 내보내기 기본값입니다.
메인 파일
앱에 더 이상 단일 카운터가 아니라 카운터 목록이 표시되기 때문에 index.js 를 조정하여 방금 만든 새 구성요소 CounterList 를 렌더링하도록 해야 합니다. 또한, 이제 적절한 WordPress 저장소가 있으므로 단일 카운터의 상태를 관리하기 위해 2부에서 작성한 모든 코드를 제거할 수도 있습니다.
이 모든 것을 고려하면 index.js 는 다음과 같아야 합니다.
// Import dependencies import { render } from '@wordpress/element'; import './store'; import CounterList from './components/counter-list'; // Render component in DOM const wrapper = document.getElementById( 'react-example-wrapper' ); render( <CounterList />, wrapper ); 보시다시피 상점과 CounterList 구성 요소를 가져오고 DOM 노드에서 @wordpress/element 패키지의 render 기능을 사용하여 후자를 렌더링합니다.
불행히도 (아직) 아무 것도 예상대로 작동하지 않지만 이는 구성 요소를 저장소에 연결하지 않았기 때문입니다.
React 구성 요소를 Redux 스토어에 연결하는 방법
멋있는! 우리가 필요로 하는 모든 것이 준비되었으며 목표에서 한 발짝밖에 남지 않았습니다. 한편으로 우리는 애플리케이션의 상태를 쿼리하기 위한 선택기와 이를 업데이트하기 위한 조치가 있는 저장소가 있습니다. 반면 UI에서 이 상태를 시각화하는 데 필요한 구성 요소가 있습니다. 누락 된 유일한 것은 두 조각을 함께 접착하는 것입니다 ...
CounterList 를 스토어에 연결하기
CounterList 는 두 가지 속성이 필요한 간단한 구성 요소입니다. 한편으로는 애플리케이션에서 활성화된 모든 구성 요소의 식별자가 포함된 목록이 필요합니다. counterIds . 한편, 새로운 카운터를 추가하기 위한 콜백도 기대합니다: addCounter .
튜토리얼의 3부에서 스토어에서 정의한 선택기와 작업을 살펴보면 그러한 선택기와 작업이 있음을 알 수 있습니다. 상점에는 실제로 "우리가 가진 모든 구성 요소의 식별자가 포함된 목록"을 반환하는 getCounterIds 선택기가 있습니다. 또한 앱에 새 카운터를 추가하는 addCounter 작업이 있습니다(고유 식별자를 제공한 경우). 이제 컴포넌트에서 어떻게 사용할 수 있는지 봅시다.

이미 상점을 등록하는 데 사용하고 있는 @wordpress/data 패키지는 상점에서 파생된 속성으로 기존 구성요소를 확장하는 몇 가지 고차 구성요소( withSelect 및 withDispatch )를 제공합니다. 즉, 구성 요소에 withSelect 및/또는 withDispatch 를 적용하면 저장소에 정의된 값을 사용하여 원하는 props 로 해당 구성 요소를 보강할 수 있습니다.
예를 들어 다음과 같이 살펴보겠습니다.
import { withSelect, withDispatch } from '@wordpress/data'; import { v4 as uuid } from 'uuid'; import Counter from './counter'; const CounterList = ( { addCounter, counterIds } ) => ( <div> { counterIds.map( ( id ) => ( <Counter key={ id } counterId={ id } /> ) ) } <button onClick={ addCounter }>Add Counter</button> </div> ); const withCounterIds = withSelect( ( select ) => { const { getCounterIds } = select( 'react-example/counters' ); return { counterIds: getCounterIds(), }; } ); const withCounterAdder = withDispatch( ( dispatch ) => { const { addCounter } = dispatch( 'react-example/counters' ); return { addCounter: () => addCounter( uuid() ), }; } ); export default withCounterAdder( withCounterIds( CounterList ) );여기에서 많은 일이 일어나고 있다는 것을 알고 있으므로 한 번에 한 단계씩 진행해 보겠습니다.
-
CounterList의 확장 버전에서 우리가 가장 먼저 하는 일은 우리가 필요로 하는 새로운 의존성을import것입니다. 특히withSelect및withDispatch를 가져옵니다. 그런 다음 이상한 가져오기가 있습니다:uuid. 이것은 고유한 임의 식별자를 생성하는 패키지이며, 이것이 필요한 이유를 잠시 후에 알게 될 것입니다. - 구성 요소 자체(
CounterList)는 변경되지 않았습니다. 여전히 UI를 렌더링하는 데 필요한 두 가지 소품, 즉 카운터 ID 목록과 새 카운터를 추가하는 작업을 얻을 것이라고 가정합니다. - 다음으로
withSelect를 사용합니다.withSelect는 고차 컴포넌트이지만 "다른 함수를 반환하는 함수"로 생각할 수 있습니다. 결과 기능은 기존 구성 요소의 기능을 보강하는 것입니다(계속 읽으십시오. 그러면 곧 모두 이해될 것입니다).-
withSelect를 사용하려면 먼저 함수를 매개변수로 사용하여 호출해야 합니다. 우리의 경우 익명 함수를 만들었습니다. - 익명 함수에는 상점에 등록된 모든 선택기에 액세스할 수 있는
select인수가 있습니다. - 이 익명 함수에서 가장 먼저 하는 일은
react-example/counters저장소에서getCounterIds선택기를 검색하는 것입니다. - 이 익명 함수의 결과는
CounterList구성 요소에 추가하려는 소품입니다. 이 경우getCounterIds에 의해 값이 검색된 식별자counterIds의 목록일 뿐입니다. -
withCounterIds의 결과를withSelect에 저장합니다(이 결과는 고차 구성 요소 또는 새 함수인 경우). 이제 이 고차 구성 요소/함수를 다음과 같이 원하는 구성 요소에 적용할 수 있습니다.withCounterIds(MyComponent). 그렇게 하면MyComponent가 이제react-example/counters저장소에서 가져온 값으로counterIds속성을 적절하게 설정하게 됩니다.
-
- 그런 다음
withDispatch고차 구성 요소/함수를 사용합니다. 그 작업은withSelect와 정확히 동일하지만 스토어 선택기에 대한 액세스 권한을 제공하는 대신 작업에 대한 액세스 권한을 제공합니다.-
withDispatch는withSelect와 같이 매개변수로 함수를 기대합니다. - 첫 번째 매개변수는 이제
dispatch라고 합니다. 스토어 작업에 액세스하는 데 사용합니다. -
dispatch를 사용하여react-example/counters저장소에서addCounter액션을 얻습니다. - 컴포넌트가 기대하는
addCounter함수는 인수가 없는 함수입니다. 그러나 우리 상점의addCounter작업에는 인수가 필요합니다. 새 카운터에 있어야 하는 고유 식별자입니다. 이 불일치를 해결하기 위해 우리가 해야 할 일은CounterList구성 요소가 기대하는 소품과 일치하는 익명 함수(즉, 인수가 없는 함수)를 반환하는 것입니다. 이 함수의 실행은 저장소의 실제 작업을 호출합니다. 상점의 작업에는 고유 식별자가 필요하므로 propaddCounter를 호출할 때마다uuid패키지를 사용하여 고유 ID를 생성합니다. -
withCounterAdder는 이withDispatch의 결과이며withCounterIds와 유사합니다. 이 고차 구성 요소를 기존 구성 요소에 적용하면 기존 구성 요소에addCounter소품이 제공되며 이는 정확히 우리가 원했던 것입니다.
-
- 마지막으로, 먼저
withCounterIds고차 구성 요소를 적용하여CounterList구성 요소를 확장하고(CounterList가CounterList목록을 수신하도록) 그런 다음counterIds를 적용withCounterAdder(addCounter소품을 수신하도록)
그리고 그게 다야! 이제 첫 번째 구성 요소를 상점과 성공적으로 연결했습니다. 즉, 이제 UI에서 카운터를 추가하고 볼 수 있어야 합니다.
각 Counter 를 매장에 연결
이제 Counter 구성 요소를 조정하여 상점에서 필요한 소품을 가져옵니다. 상상할 수 있듯이 우리가 해야 할 일은 방금 했던 프로세스를 반복하는 것뿐입니다. withSelect 및 withDispatch 를 통해 react-example/counters 에서 몇 가지 선택기와 작업을 사용하고 결과로 나오는 고차 구성 요소를 Counter 에 적용합니다.
최종 소스 코드부터 살펴보겠습니다.
import { compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; const Counter = ( { value, onDelete, onIncrease, onDecrease } ) => ( <div> <div>Counter: <strong>{ value }</strong></div> <button onClick={ onIncrease }>+</button> <button onClick={ onDecrease }>-</button> <button onClick={ onDelete }>Delete</button> </div> ); const withValue = withSelect( ( select, { counterId } ) => { const { getCounterValue } = select( 'react-example/counters' ); return { value: getCounterValue( counterId ), }; } ); const withActions = withDispatch( ( dispatch, { counterId, value } ) => { const { setCounterValue, removeCounter, } = dispatch( 'react-example/counters' ); return { onIncrease: () => setCounterValue( counterId, value + 1 ), onDecrease: () => setCounterValue( counterId, value - 1 ), onDelete: () => removeCounter( counterId ), }; } ); export default compose( withValue, withActions )( Counter );- 우선, 필요한 종속성을
import. - 구성 요소 자체인
Counter도 변경되지 않습니다. 그것은 여전히 4개의 props를 기대합니다:value,onDelete,onIncrease, 그리고onDecrease, 이제 우리는 스토어에서 가져올 것이라고 알고 있습니다. - 먼저
withSelect를 사용하여 매장에서 카운터value을 검색합니다.- 상점은 많은 카운터의 값을 유지하므로 원하는 카운터를 정확히 알려야 합니다. 운 좋게도 CounterList가 각 카운터 ID를 구성 요소에 매핑할 때
CounterList소품을 사용하여 각Counter를 렌더링counterId. 기억하시나요? 글쎄, 그것이 우리가 관심있는 정확한 가치를 우리 가게에 말할 수있는 방법입니다 ... - 이 익명 함수의 본문은
CounterList에서 본 예제와 매우 유사합니다. 상점에서getCounterValue선택기를 가져counterId소품을 사용하여 올바른value을 반환하기만 하면 됩니다. 이제 익명 함수에 두 번째 인수인 구성 요소의props목록이 있습니다.
- 상점은 많은 카운터의 값을 유지하므로 원하는 카운터를 정확히 알려야 합니다. 운 좋게도 CounterList가 각 카운터 ID를 구성 요소에 매핑할 때
- 다음으로
withDispatch를 사용하여Counter에 필요한 작업을 채웁니다.- 상점에는
setCounterValue및removeCounter작업이 있다는 것을 알고 있습니다. - 두 작업 모두 업데이트 또는 삭제를 위해 카운터의 식별자가 필요합니다. 이 식별자는 방금 보았듯이
withDispatch에서 정의한 익명 함수의 두 번째 인수에서 사용할 수 있습니다. -
setCounterValue는 또한 우리가 설정하려는 새 값을 알아야 합니다.Counter구성 요소는 기능해야 하기 때문에(하나는 현재 값을 증가시키고 다른 하나는 현재 값을 감소시킴) 하나를 더(또는 빼기)할 수 있도록 현재 값이 무엇인지 알아야 합니다. 운 좋게도withSelect를 사용하여 카운터의 현재 값을 검색했습니다. 이는value라는 prop이 있음을 의미합니다. -
withDispatch를 적용한 결과는 구성 요소가 기대하는 세 가지 기능을 갖는 고차 구성 요소입니다.onIncrease는counterId의 현재 값에1을 더하는 익명 함수입니다.onDecrease는 동일한 작업을 수행하지만1을 빼는 또 다른 익명 함수입니다.onDelete는 저장소에서removeCounter작업을 사용하는 함수입니다.
- 상점에는
- 마지막으로 방금 만든 고차 구성 요소(
withValue및withActions)를Counter구성 요소에 적용합니다. 이번에는@wordpress/compose에서 제공하는compose도우미 기능을 사용했지만 이전에CounterList에서 했던 것과 정확히 동일하다는 점에 유의하세요.
그리고 그게 다야! 이제 상점에 반응하고 상점을 수정하는 여러 구성 요소가 있는 작업 예제가 있습니다!
요약하자면
WordPress의 React/Redux에 대한 이 짧은 소개를 통해 우리는 좋은 UI를 만드는 데 필요한 모든 요소를 보았습니다. 본질적으로 구성 요소는 속성을 수신하고 HTML을 생성하는 순수한 기능이어야 하며, 스토어를 사용하여 UI와 독립적으로 앱 상태를 유지하는 방법, 서로 통합하는 방법을 살펴보았습니다.
상점에서 값을 가져오도록 React 구성 요소를 확장하는 것은 withSelect 및 withDispatch 를 사용하고 필요한 누락된 소품을 추가하는 고차 구성 요소를 생성하는 것만큼 쉽습니다.
Unsplash에서 Dmitry Nucky Thompson의 추천 이미지.
