React.js 생태계 탐색
게시 됨: 2022-03-11JavaScript Land의 혁신 속도는 너무 빨라서 일부 사람들은 그것이 비생산적이라고 생각합니다. 도서관은 몇 달 만에 얼리 어답터 장난감에서 최첨단 기술로 변모할 수 있습니다. 적어도 1년 동안 관련성을 유지할 도구를 식별할 수 있다는 것은 그 자체로 하나의 예술이 되었습니다.
2년 전 React.js가 출시되었을 때 저는 Angular를 막 배우고 있었고 React를 모호한 또 다른 템플릿 라이브러리라고 재빨리 일축했습니다. 그 2년 동안 Angular는 JavaScript 개발자들 사이에서 발판을 마련했으며 현대 JS 개발과 거의 동의어가 되었습니다. 매우 보수적인 기업 환경에서도 Angular를 보기 시작했고 밝은 미래를 당연하게 여겼습니다.
그런데 갑자기 이상한 일이 일어났습니다. Angular는 Osborne 효과 또는 "사전 발표에 의한 죽음"의 희생자가 된 것 같습니다. 팀은 첫째, Angular 1에서 명확한 마이그레이션 경로 없이 Angular 2가 완전히 달라질 것이며, 두 번째로 Angular 2는 앞으로 1년 정도는 사용할 수 없을 것이라고 발표했습니다. 그것은 새로운 웹 프로젝트를 시작하고 싶어하는 사람에게 무엇을 말합니까? 새 버전 릴리스로 인해 쓸모 없게 될 프레임워크에서 새 프로젝트를 작성하시겠습니까?
개발자들 사이의 이러한 불안은 커뮤니티에서 자신을 확립하려는 React의 손에 영향을 미쳤습니다. 그러나 React는 항상 "MVC"에서 "V"로 자신을 마케팅했습니다. 이것은 완전한 프레임워크로 작업하는 데 익숙한 웹 개발자들 사이에서 어느 정도 좌절감을 불러일으켰습니다. 누락된 부분은 어떻게 채우나요? 직접 작성해야 하나요? 기존 라이브러리만 사용해야 합니까? 그렇다면 어느 것입니까?
물론 페이스북(React.js의 제작자)은 부족한 "M" 및 "C" 기능을 채우겠다고 약속한 Flux 워크플로라는 구멍에 또 하나의 에이스가 있었습니다. 상황을 더욱 흥미롭게 만들기 위해 Facebook은 Flux가 프레임워크가 아니라 "패턴"이며 Flux의 구현은 패턴의 한 예일 뿐이라고 말했습니다. 그들의 말대로, 그들의 구현은 정말 단순했고 일을 진행하기 위해 장황하고 반복적인 상용구를 많이 작성해야 했습니다.
오픈 소스 커뮤니티가 구출에 나섰고 1년 후 우리는 수십 개의 Flux 라이브러리와 이를 비교하기 위한 일부 메타 프로젝트를 갖게 되었습니다. 이것은 좋은 일입니다. Facebook은 이미 만들어진 기업 프레임워크를 공개하는 대신 커뮤니티의 관심을 불러일으키고 사람들이 자신만의 솔루션을 찾도록 독려했습니다.
이 접근 방식에는 한 가지 흥미로운 부작용이 있습니다. 완전한 프레임워크를 얻기 위해 다양한 라이브러리를 결합해야 하는 경우 공급업체 종속을 효과적으로 탈출하고 자체 프레임워크 구축 중에 나타나는 혁신을 쉽게 재사용할 수 있습니다. 다른 곳.
이것이 React와 관련된 새로운 것들이 매우 흥미로운 이유입니다. 대부분은 다른 JavaScript 환경에서 쉽게 재사용할 수 있습니다. React를 사용할 계획이 없더라도 생태계를 살펴보는 것은 영감을 줍니다. 강력하지만 비교적 구성하기 쉬운 모듈 번들러 Webpack을 사용하여 빌드 시스템을 단순화하거나 Babel 컴파일러를 사용하여 지금 ECMAScript 6 및 ECMAScript 7을 작성하기 시작할 수 있습니다.
이 기사에서는 사용 가능한 몇 가지 흥미로운 기능과 라이브러리를 살펴보겠습니다. 그럼 이제 리액트 생태계를 살펴보자!
시스템 구축
빌드 시스템은 새로운 웹 애플리케이션을 생성할 때 가장 먼저 신경써야 할 부분입니다. 빌드 시스템은 스크립트를 실행하기 위한 도구일 뿐만 아니라 JavaScript 세계에서 일반적으로 애플리케이션의 일반적인 구조를 형성합니다. 빌드 시스템이 다루어야 하는 가장 중요한 작업은 다음과 같습니다.
- 외부 및 내부 종속성 관리
- 컴파일러 및 전처리기 실행
- 생산을 위한 자산 최적화
- 개발 웹 서버, 파일 감시자, 브라우저 리로더 실행
최근 몇 년 동안 Bower 및 Grunt를 사용한 Yeoman 워크플로는 최신 프론트엔드 개발의 성삼위일체로 제시되었으며, 최근 Grunt에서 Gulp로 전환하는 보다 진보적인 포크와 함께 상용구 생성, 패키지 관리 및 공통 작업 실행 문제를 각각 해결했습니다.
React 환경에서는 이것들을 잊어도 무방합니다. 사용할 수 없다는 것은 아니지만 Webpack과 오래된 NPM을 사용하여 벗어날 수 있습니다. 어떻게 그게 가능합니까? Webpack은 Node.js 세계에서 흔히 볼 수 있는 CommonJS 모듈 구문을 브라우저에서도 구현하는 모듈 번들러입니다. 프론트 엔드에 대한 또 다른 패키지 관리자를 배울 필요가 없기 때문에 실제로는 일을 더 간단하게 만듭니다. NPM을 사용하고 서버와 프런트 엔드 간에 종속성을 공유하기만 하면 됩니다. 또한 각 파일에 지정된 종속성 가져오기에서 유추되고 전체 무리가 로드 가능한 하나의 스크립트에 올바르게 연결되기 때문에 JS 파일을 올바른 순서로 로드하는 문제를 처리할 필요가 없습니다.
더 매력적으로 만들기 위해 Webpack은 이전 사촌인 Browserify와 달리 다른 자산 유형도 처리할 수 있습니다. 예를 들어 로더를 사용하면 자산 파일을 참조 파일을 인라인하거나 로드하는 JavaScript 함수로 변환할 수 있습니다. 따라서 HTML에서 자산을 수동으로 사전 처리하고 참조하는 것은 잊어버리십시오. JavaScript에서 CSS/SASS/LESS 파일만 require
하면 Webpack이 간단한 구성 파일로 나머지를 처리합니다. Webpack은 또한 개발 웹 서버와 파일 감시자를 포함합니다. 또한 package.json
의 "scripts"
키를 사용하여 셸 원 라이너를 정의할 수 있습니다.
{ "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }
이것이 Gulp와 Bower를 교체하는 데 필요한 전부입니다. 물론 애플리케이션 상용구를 생성하는 데 Yeoman을 계속 사용할 수 있습니다. 원하는 항목에 대한 Yeoman 생성기가 없다고 낙심하지 마십시오(가장 최첨단 라이브러리에는 종종 생성기가 없습니다). 여전히 GitHub에서 일부 상용구를 복제하고 해킹할 수 있습니다.
내일, 오늘의 ECMAScript
JavaScript 언어 개발의 속도는 최근 몇 년 동안 상당히 증가했으며 특정 기간 동안 단점을 제거하고 언어를 안정화한 후 이제 강력한 새 기능이 추가되는 것을 볼 수 있습니다. ECMAScript 6(ES6) 사양 초안이 완성되었지만 아직 공식화되지 않았지만 이미 널리 채택되고 있습니다. ECMAScript 7(ES7)에 대한 작업이 진행 중이지만 많은 기능이 이미 최첨단 라이브러리에서 채택되고 있습니다.
이것이 어떻게 가능한지? Internet Explorer에서 지원될 때까지 이러한 반짝이는 새로운 JavaScript 기능을 활용할 수 없다고 생각할 수도 있지만 다시 생각해 보십시오. ES 트랜스파일러는 이미 너무 보편화되어 적절한 브라우저 지원 없이도 할 수 있습니다. 현재 사용 가능한 최고의 ES 트랜스파일러는 Babel입니다. 최신 ES6+ 코드를 가져와서 바닐라 ES5로 변환하므로 새로운 ES 기능이 발명되는 즉시 사용할 수 있습니다. 빠르게).
최신 JavaScript 기능은 모든 프론트 엔드 프레임워크에서 유용하며 React는 최근 ES6 및 ES7 사양과 잘 작동하도록 업데이트되었습니다. 이러한 새로운 기능은 React로 개발할 때 많은 골칫거리를 제거해야 합니다. 가장 유용한 몇 가지 추가 사항과 이것이 React 프로젝트에 어떻게 도움이 되는지 살펴보겠습니다. 나중에 우리는 이 개선된 구문을 활용하면서 React와 함께 몇 가지 유용한 도구와 라이브러리를 사용하는 방법을 볼 것입니다.
ES6 수업
객체 지향 프로그래밍은 강력하고 널리 채택된 패러다임이지만 JavaScript가 이를 받아들이는 것은 다소 이색적입니다. Backbone, Ember, Angular 또는 React와 같은 대부분의 프론트 엔드 프레임워크는 따라서 클래스를 정의하고 객체를 생성하는 고유한 방식을 채택했습니다. 그러나 ES6에서는 이제 JavaScript에 기존 클래스가 있으며 자체 구현을 작성하는 대신 이를 사용하는 것이 합리적입니다. 따라서 다음 대신:
React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })
우리는 쓸 수있다:
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }
보다 정교한 예를 보려면 이전 구문을 사용하는 다음 코드를 고려하십시오.
React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });
ES6 버전과 비교:
class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }
여기서 React 수명 주기 메서드 getDefaultProps
및 getInitialState
는 더 이상 필요하지 않습니다. getDefaultProps
는 정적 클래스 변수 defaultProps
가 되며 초기 상태는 생성자에서 정의됩니다. 유일한 단점은 메서드가 더 이상 자동 바인딩되지 않으므로 JSX에서 핸들러를 호출할 때 bind
를 사용해야 한다는 것입니다.
데코레이터
데코레이터는 ES7의 유용한 기능입니다. 그것들을 사용하면 다른 함수 안에 래핑하여 함수나 클래스의 동작을 보강할 수 있습니다. 예를 들어, 일부 구성 요소에 대해 동일한 변경 처리기를 갖고 싶지만 상속 반패턴에 커밋하고 싶지 않다고 가정해 보겠습니다. 대신 클래스 데코레이터를 사용할 수 있습니다. 다음과 같이 데코레이터를 정의합시다.
addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }
여기서 중요한 것은 addChangeHandler
함수가 대상 클래스의 프로토타입에 changeHandler
함수를 추가한다는 것입니다.
데코레이터를 적용하려면 다음과 같이 작성할 수 있습니다.
MyClass = changeHandler(MyClass)
ES7 구문을 사용하여 더 우아하게:
@addChangeHandler class MyClass { ... }
changeHandler
함수 자체의 내용에 관해서는 React에 양방향 데이터 바인딩이 없기 때문에 React에서 입력 작업은 지루할 수 있습니다. changeHandler
함수는 이를 더 쉽게 만들려고 합니다. 첫 번째 매개변수는 상태 개체의 key
를 지정하며 이는 입력에 대한 데이터 개체 역할을 합니다. 두 번째 매개변수는 입력 필드의 값이 저장될 속성입니다. 이 두 매개변수는 bind
키워드를 사용하여 JSX에서 설정됩니다.
@addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }
사용자가 사용자 이름 필드를 변경하면 더 많은 사용자 정의 핸들러를 정의할 필요 없이 해당 값이 this.state.login.username
에 저장됩니다.
화살표 함수
JavaScript의 동적 this
컨텍스트는 다소 직관적이지 않지만 중첩된 함수의 this
컨텍스트가 클래스 내에서도 전역으로 재설정되기 때문에 개발자에게 끊임없는 고통이었습니다. 이 문제를 해결하려면 일반적으로 this
외부 범위 변수(보통 _this
)에 저장하고 내부 함수에서 사용해야 합니다.
class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }
새로운 ES6 구문을 사용하면 function(x){
를 (x) => {
로 다시 작성할 수 있습니다. 이 "화살표" 메서드 정의는 this
외부 범위에 올바르게 바인딩할 뿐만 아니라 상당히 짧아서 많은 비동기 코드를 작성할 때 확실히 중요합니다.
onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }
구조 분해 할당
ES6에 도입된 구조 분해 할당을 사용하면 할당의 왼쪽에 복합 객체를 가질 수 있습니다.
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
이것은 좋은데 실제로 React에서 어떻게 도움이 될까요? 다음 예를 고려하십시오.
function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }
구조화를 사용하면 몇 번의 키 입력을 저장할 수 있습니다. 키 리터럴 {url, method, params}
에는 키와 이름이 같은 범위의 값이 자동으로 할당됩니다. 이 관용구는 꽤 자주 사용되며 반복을 제거하면 코드에서 오류가 발생하기 쉽습니다.
function makeRequest(url, method, params) { var config = {url, method, params}; ... }
Destructuring은 또한 모듈의 하위 집합만 로드하는 데 도움이 될 수 있습니다.
const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }
인수: 기본값, 나머지 및 스프레드
함수 인수는 ES6에서 더 강력합니다. 마지막으로 기본 인수를 설정할 수 있습니다.
function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET
다루기 힘든 arguments
개체를 사용하는 데 지쳤습니까? 새 사양을 사용하면 나머지 인수를 배열로 가져올 수 있습니다.
function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }
그리고 apply()
호출이 마음에 들지 않으면 배열을 함수 인수로 분산 할 수 있습니다.
myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);
생성기 및 비동기 함수
ES6은 JavaScript 생성기를 도입했습니다. 제너레이터는 기본적으로 실행을 일시 중지했다가 나중에 다시 시작하여 상태를 기억할 수 있는 JavaScript 함수입니다. yield
키워드를 만날 때마다 실행이 일시 중지되고 yield
인수가 호출 객체로 다시 전달됩니다.
function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }
다음은 작동 중인 이 생성기의 예입니다.
> var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }
생성기 함수를 호출하면 첫 번째 yield
까지 실행된 다음 중지됩니다. next()
를 호출한 후 첫 번째 값을 반환하고 실행을 재개합니다. 각 yield
는 다른 값을 반환하지만 세 번째 호출 후 생성기 함수가 종료되고 next()
에 대한 각 후속 호출은 { value: undefined, done: true }
를 반환합니다.
물론 생성기의 요점은 정교한 숫자 시퀀스를 만드는 것이 아닙니다. 흥미로운 부분은 비동기 프로그램 흐름을 제어하고 마지막으로 이러한 성가신 콜백 함수를 제거하는 데 사용할 수 있는 함수 실행을 중지하고 다시 시작하는 기능입니다.
이 아이디어를 시연하려면 먼저 비동기 함수가 필요합니다. 일반적으로 약간의 I/O 작업이 있지만 간단하게 하기 위해 setTimeout
을 사용하고 약속을 반환하겠습니다. (ES6은 또한 JavaScript에 네이티브 프라미스를 도입했습니다.)
function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }
다음으로 소비자 함수가 필요합니다.
function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }
이 함수는 임의의 생성기를 인수로 사용하고 yield
할 값이 있는 한 계속해서 next()
를 호출합니다. 이 경우 산출된 값은 약속이므로 약속이 해결될 때까지 기다려야 하며 loop()
와 함께 재귀를 사용하여 중첩된 함수에 대한 루프를 달성해야 합니다.
반환 값은 then()
처리기에서 확인되고 외부 범위에서 정의되고 next(value)
호출로 전달되는 value
로 전달됩니다. 이 호출은 값을 해당 yield 표현식의 결과로 만듭니다. 이것은 이제 콜백 없이 비동기적으로 작성할 수 있음을 의미합니다.
function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);
생성기 myGenerator
는 각 yield
에서 일시 중지되어 소비자가 해결된 약속을 전달할 때까지 기다립니다. 그리고 실제로 계산된 숫자가 1초 간격으로 콘솔에 표시되는 것을 볼 수 있습니다.
Double 1 = 2 Double 2 = 4 Double 3 = 6
이것은 기본 개념을 보여주지만 프로덕션 환경에서는 이 코드를 사용하지 않는 것이 좋습니다. 대신 co와 같이 잘 테스트된 라이브러리를 선택하십시오. 이렇게 하면 오류 처리를 포함하여 수율이 있는 비동기 코드를 쉽게 작성할 수 있습니다.
co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });
따라서 이 예제에서는 ES6 생성기를 사용하여 콜백 없이 비동기 코드를 작성하는 방법을 보여줍니다. ES7은 async
및 await
키워드를 도입하고 생성기 라이브러리의 필요성을 완전히 제거함으로써 이 접근 방식을 한 단계 더 발전시켰습니다. 이 기능을 사용하는 이전 예제는 다음과 같습니다.
async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };
제 생각에 이것은 JavaScript에서 비동기 코드로 작업하는 데 어려움을 덜어줍니다. React 뿐만 아니라 다른 모든 곳에서도 마찬가지입니다.
제너레이터는 더 간결하고 간단할 뿐만 아니라 콜백으로 구현하기 매우 어려운 기술을 사용할 수 있게 해줍니다. 제너레이터의 장점은 Node.js용 koa 미들웨어 라이브러리입니다. Express를 대체하는 것을 목표로 하고 있으며 그 목표를 위해 미들웨어 체인이 다운스트림 (클라이언트 요청 포함)뿐만 아니라 업스트림 으로 흐르며 서버 응답을 추가로 수정할 수 있도록 하는 하나의 킬러 기능이 함께 제공됩니다. 다음 koa 서버 예를 고려하십시오.
// Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000);
응답 미들웨어는 응답 본문을 설정하는 응답 핸들러로 다운스트림을 yield
yield
이후)에서 this.body
의 추가 수정과 시간 로깅과 같은 다른 기능이 허용됩니다. 업스트림과 다운스트림이 동일한 기능 컨텍스트를 공유하기 때문입니다. 이것은 동일한 작업을 수행하려는 시도가 다음과 같이 끝나는 Express보다 훨씬 강력합니다.
var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);
여기에서 이미 무엇이 잘못되었는지 알 수 있을 것입니다. "전역" start
변수를 사용하면 경합 조건이 발생하여 동시 요청으로 넌센스를 반환합니다. 솔루션은 일부 명확하지 않은 해결 방법이며 업스트림 흐름에서 응답을 수정하는 것을 잊어버릴 수 있습니다.

또한 koa를 사용할 때 생성기 비동기 워크플로를 무료로 얻을 수 있습니다.
app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);
Express에서 이 작은 예제를 다시 만드는 것과 관련된 약속과 콜백을 상상할 수 있습니다.
이 모든 Node.js 이야기가 React와 어떤 관련이 있습니까? 글쎄요, Node는 React에 적합한 백엔드를 고려할 때 첫 번째 선택입니다. Node는 JavaScript로도 작성되었기 때문에 백엔드와 프론트엔드 간의 코드 공유를 지원하므로 동형 React 웹 애플리케이션을 구축할 수 있습니다. 그러나 이에 대한 자세한 내용은 나중에 설명합니다.
플럭스 라이브러리
React는 구성 가능한 뷰 구성 요소를 만드는 데 탁월하지만 전체 애플리케이션에서 데이터와 상태를 관리하는 방법이 필요합니다. React가 Flux 애플리케이션 아키텍처에 의해 가장 잘 보완된다는 것은 거의 보편적으로 동의되었습니다. Flux를 완전히 처음 사용하는 경우 빠른 새로 고침을 권장합니다.
보편적으로 동의하지 않은 것은 많은 Flux 구현 중 어떤 것을 선택해야 하는지입니다. Facebook Flux는 명백한 선택이지만 대부분의 사람들에게 너무 장황합니다. 대체 구현은 구성에 대한 관례 접근 방식과 고차 구성 요소, 서버 측 렌더링 등을 위한 일부 편의 기능을 사용하여 필요한 상용구의 양을 줄이는 데 주로 중점을 둡니다. 다양한 인기도 측정항목을 가진 상위 경쟁자 중 일부는 여기에서 볼 수 있습니다. Alt, Reflux, Flummox, Fluxxor 및 Marty.js를 살펴보았습니다.
올바른 라이브러리를 선택하는 방법은 객관적이지 않지만 어쨌든 도움이 될 수 있습니다. Fluxxor는 내가 체크아웃한 첫 번째 라이브러리 중 하나였지만 지금은 약간 부실해 보입니다. Marty.js는 흥미롭고 많은 기능을 가지고 있지만 여전히 많은 상용구를 포함하고 일부 기능은 불필요해 보입니다. Reflux는 멋지게 보이고 약간의 견인력이 있지만 초보자에게는 다소 어렵게 느껴지며 적절한 문서도 부족합니다. Flummox와 Alt는 매우 유사하지만 Alt는 상용구가 훨씬 적고 개발이 매우 활발하며 최신 문서와 도움이 되는 Slack 커뮤니티가 있는 것 같습니다. 따라서 Alt를 선택했습니다.
대체 플럭스
Alt를 사용하면 Flux 워크플로가 성능을 잃지 않고 훨씬 간단해집니다. Facebook의 Flux 문서는 디스패처에 대해 많은 것을 말하지만 Alt에서는 디스패처가 관습에 따라 암시적으로 작업에 연결되고 일반적으로 사용자 정의 코드가 필요하지 않기 때문에 무시해도 됩니다. 이렇게 하면 저장소 , 작업 및 구성 요소 만 남게 됩니다. 이 세 가지 계층은 MVC 사고 모델에 멋지게 매핑되는 방식으로 사용될 수 있습니다. Stores는 Models , action은 Controllers , component는 Views 입니다. 주요 차이점은 Flux 패턴의 중심에 있는 단방향 데이터 흐름입니다. 즉, 컨트롤러(액션)가 뷰(구성 요소)를 직접 수정할 수 없지만 대신 뷰가 수동적으로 바인딩되는 모델(저장) 수정만 트리거할 수 있습니다. 이것은 이미 일부 깨달은 Angular 개발자에게 모범 사례였습니다.
워크플로는 다음과 같습니다.
- 구성 요소는 작업을 시작합니다.
- 상점은 작업을 수신하고 데이터를 업데이트합니다.
- 구성 요소는 저장소에 바인딩되며 데이터가 업데이트되면 다시 렌더링됩니다.
행위
Alt Flux 라이브러리를 사용할 때 작업은 일반적으로 자동 및 수동의 두 가지 방식으로 제공됩니다. 자동 작업은 generateActions
함수를 사용하여 생성되고 디스패처로 직접 이동합니다. 수동 메서드는 작업 클래스의 메서드로 정의되며 추가 페이로드와 함께 디스패처로 이동할 수 있습니다. 자동 작업의 가장 일반적인 사용 사례는 응용 프로그램의 일부 이벤트에 대해 상점에 알리는 것입니다. 수동 작업은 무엇보다도 서버 상호 작용을 처리하는 데 선호되는 방법입니다.
따라서 REST API 호출은 작업에 속합니다. 전체 워크플로는 다음과 같습니다.
- 구성 요소가 작업을 트리거합니다.
- 작업 생성자는 비동기식 서버 요청을 실행하고 결과는 페이로드로 디스패처에 전달됩니다.
- 저장소는 작업을 수신하고 해당 작업 핸들러는 결과를 인수로 수신하고 저장소는 그에 따라 상태를 업데이트합니다.
AJAX 요청의 경우 무엇보다도 JSON 데이터와 헤더를 원활하게 처리하는 axios 라이브러리를 사용할 수 있습니다. 약속이나 콜백 대신 ES7 async
/ await
패턴을 사용할 수 있습니다. POST
응답 상태가 2XX가 아니면 오류가 발생하고 반환된 데이터 또는 수신된 오류를 전달합니다.
Alt 워크플로의 간단한 예를 보려면 로그인 페이지를 살펴보겠습니다. 로그아웃 작업은 특별한 작업을 수행할 필요가 없으며 상점에 알리기만 하면 자동으로 생성할 수 있습니다. 로그인 작업은 수동이며 로그인 데이터를 작업 작성자에 대한 매개변수로 예상합니다. 서버로부터 응답을 받은 후 성공 데이터를 전달하거나 오류가 발생하면 받은 오류를 전달합니다.
class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));
백화점
Flux 저장소는 두 가지 용도로 사용됩니다. 작업 처리기가 있고 상태를 전달합니다. 로그인 페이지 예제를 계속 진행하여 이것이 어떻게 작동하는지 봅시다.
현재 로그인한 user
에 대한 user 와 현재 로그인 관련 error
에 대한 error 의 두 가지 상태 속성을 사용하여 LoginStore
를 생성해 보겠습니다. 상용구를 줄이기 위해 Alt를 사용하면 단일 함수 bindActions
로 한 클래스의 모든 작업에 바인딩할 수 있습니다.
class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...
핸들러 이름은 규칙에 따라 정의되며 해당 작업 이름 앞에 추가 on
. 따라서 login
작업은 onLogin
등에 의해 처리됩니다. 작업 이름의 첫 글자는 camelCase 스타일로 대문자로 표시됩니다. LoginStore
에는 해당 작업에 의해 호출되는 다음 핸들러가 있습니다.
... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }
구성품
저장소를 구성 요소에 바인딩하는 일반적인 방법은 일종의 React 믹스인을 사용하는 것입니다. 하지만 믹스인은 유행을 타지 않기 때문에 다른 방법이 필요합니다. 새로운 접근 방식 중 하나는 고차 구성 요소를 사용하는 것입니다. 컴포넌트를 가져와서 래퍼 컴포넌트 안에 넣습니다. 그러면 스토어에서 수신 대기하고 re-render를 호출하는 작업을 처리합니다. 우리의 컴포넌트는 props
에서 스토어의 상태를 받을 것입니다. 이 접근 방식은 또한 최근 유행하고 있는 똑똑하고 멍청한 구성 요소로 코드를 구성하는 데 도움이 됩니다. Alt의 경우 구성 요소 래퍼는 AltContainer
에 의해 구현됩니다.
export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }
LoginPage
구성 요소는 앞서 소개한 changeHandler
데코레이터도 사용합니다. LoginStore
의 데이터는 로그인에 실패한 경우 오류를 표시하는 데 사용되며 재렌더링은 AltContainer
에서 처리합니다. 로그인 버튼을 클릭하면 login
작업이 실행되어 Alt 플럭스 워크플로가 완료됩니다.
@changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }
동형 렌더링
동형 웹 응용 프로그램은 기존 단일 페이지 응용 프로그램의 가장 큰 작업 중 일부를 해결하기 때문에 요즘 뜨거운 주제입니다. 이러한 응용 프로그램에서 마크업은 브라우저의 JavaScript에 의해 동적으로 생성됩니다. 그 결과 JavaScript가 꺼져 있는 클라이언트, 특히 검색 엔진 웹 크롤러에서 콘텐츠를 사용할 수 없습니다. 이것은 귀하의 웹 페이지가 인덱싱되지 않았으며 검색 결과에 나타나지 않음을 의미합니다. 이 문제를 해결하는 방법이 있지만 최적의 방법은 아닙니다. 동형 접근 방식은 서버에서 단일 페이지 애플리케이션의 요청된 URL을 미리 렌더링하여 이 문제를 해결하려고 시도합니다. Node.js를 사용하면 서버에 JavaScript가 있습니다. 즉, React도 서버 측에서 실행할 수 있습니다. 이것은 너무 어렵지 않아야합니다.
한 가지 장애물은 일부 Flux 라이브러리, 특히 싱글톤을 사용하는 라이브러리는 서버 측 렌더링에 어려움이 있다는 것입니다. 싱글톤 Flux 저장소와 서버에 대한 여러 동시 요청이 있는 경우 데이터가 혼합됩니다. 일부 라이브러리는 Flux 인스턴스를 사용하여 이 문제를 해결하지만, 특히 코드에서 해당 인스턴스를 전달해야 하는 다른 단점이 있습니다. Alt는 Flux 인스턴스도 제공하지만 싱글톤으로 서버 측 렌더링 문제도 해결했습니다. 각 요청 후에 저장소를 플러시하므로 각 동시 요청이 깨끗한 상태로 시작됩니다.
서버 측 렌더링 기능의 핵심은 React.renderToString
에 의해 제공됩니다. 전체 React 프론트 엔드 애플리케이션도 서버에서 실행됩니다. 이렇게 하면 클라이언트 측 JavaScript가 마크업을 생성할 때까지 기다릴 필요가 없습니다. 액세스한 URL에 대해 서버에 미리 빌드되어 HTML로 브라우저에 전송됩니다. 클라이언트 JavaScript가 실행될 때 서버가 중단된 부분을 선택합니다. 이를 지원하기 위해 Alt와 함께 사용하기 위한 Iso 라이브러리를 사용할 수 있습니다.
먼저 alt.bootstrap
을 사용하여 서버에서 Flux를 초기화합니다. Flux 저장소를 렌더링용 데이터로 미리 채울 수 있습니다. 또한 클라이언트 측 Router
의 기능인 URL에 대해 어떤 구성 요소를 렌더링할지 결정해야 합니다. 우리는 alt
의 싱글톤 버전을 사용하고 있으므로 각 렌더링 후에 다른 요청을 위해 저장소를 정리하도록 alt.flush()
해야 합니다. iso
추가 기능을 사용하여 Flux의 상태가 HTML 마크업으로 직렬화되어 클라이언트가 선택할 위치를 알 수 있습니다.
// We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });
클라이언트 측에서는 서버 상태를 선택하고 데이터로 alt
를 부트스트랩합니다. 그런 다음 대상 컨테이너에서 Router
및 React.render
를 실행하여 필요에 따라 서버 생성 마크업을 업데이트합니다.
Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })
사랑스러운!
유용한 프론트엔드 라이브러리
React 생태계에 대한 가이드는 특히 React와 잘 작동하는 몇 가지 프론트 엔드 라이브러리를 언급하지 않고는 완전하지 않을 것입니다. 이러한 라이브러리는 CSS 레이아웃 및 컨테이너, 스타일 양식 및 버튼, 유효성 검사, 날짜 선택 등 거의 모든 웹 응용 프로그램에서 발견되는 가장 일반적인 작업을 다룹니다. 이러한 문제가 이미 해결되었을 때 바퀴를 재발명하는 것은 의미가 없습니다.
반응 부트스트랩
Twitter의 부트스트랩 프레임워크는 CSS 작업에 많은 시간을 할애하고 싶지 않은 모든 웹 개발자에게 엄청난 도움이 되었기 때문에 일반화되었습니다. 특히 프로토타이핑 단계에서 Bootstrap은 필수 불가결합니다. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:
<Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>
Personally, I cannot escape the feeling that this is what HTML should always have been like.
If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.
반응 라우터
React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:
<Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>
React Router also provides a Link
component that you can use for navigation in your application, specifying only the route name:
<nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>
There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active
class on them manually all the time, you can use react-router-bootstrap and write code like this:
<Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>
No additional setup is necessary. Active links will take care of themselves.
Formsy-React
Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:
import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }
Calendar and Typeahead
Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:
import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
Conclusion - React.JS
In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.
If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.
읽어 주셔서 감사합니다!