이 글은 React의 고급 디자인과 제어에 대해 소개합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.
컨트롤 - 이 개념은 프로그래밍에서 매우 중요합니다. 예를 들어, 제어를 위한 "바퀴" 캡슐화 계층과 비즈니스 소비 계층 간의 "경쟁"은 매우 흥미로운 주제입니다. 이는 React 세계에서도 다르지 않습니다. 표면적으로는 물론 "휠"이 가능한 한 많은 것을 제어할 수 있기를 바랍니다. 추상화 계층이 처리하는 논리가 많을수록 비즈니스를 호출할 때 신경 써야 할 사항이 줄어들고 더 편리해지기 때문입니다. 사용합니다. 그러나 일부 디자인은 "감히 너무 멀리 나아가지 않습니다". 통제권을 둘러싸고 '바퀴'와 기업 사이의 줄다리기가 매우 흥미롭다.
동시에 제어 기능은 구성 요소 설계와도 밀접한 관련이 있습니다. 원자 구성 요소와 같은 원자 구성 요소 설계는 원자 구성 요소 개념 외에 분자 구성 요소도 있습니다. 분자든 원자든 비즈니스 문제를 해결하는 데에는 모두 존재 이유가 있습니다.
이 글에서는 React 프레임워크를 배경으로 개발 중 제어에 대한 내 생각과 결론을 이야기할 것입니다. React를 사용하지 않더라도 원칙적으로는 읽기에 지장이 없습니다
글을 시작하기 전에 책 한 권을 소개하고 싶습니다.
작년부터 유명한 기술 전문가인 Yan Haijing과 공동 집필 여행을 시작했습니다. 올해 우리가 공동으로 다듬은 책 "React State Management and Isomorphism in Practice"가 드디어 정식 출간되었습니다. 출판되었습니다! 이 책은 React 기술 스택을 핵심으로 React 사용법 소개를 바탕으로 Redux 아이디어를 소스 코드 수준에서 분석하고, 서버 측 렌더링 및 동형 애플리케이션의 아키텍처 패턴에도 중점을 둡니다. 이 책에는 사용자에게 React 기술 스택에 대한 문을 열어줄 뿐만 아니라 독자의 최첨단 분야에 대한 전반적인 이해를 높이는 많은 프로젝트 예제가 포함되어 있습니다.
React에 처음 들어갔을 때 제어의 개념에 대해 가장 먼저 접하게 되는 것은 제어되는 구성 요소와 제어되지 않는 구성 요소입니다. 이 두 개념은 종종 형태와 연관됩니다. 대부분의 경우 양식 및 입력 상자와 같은 상태 제어를 구현하기 위해 제어되는 구성 요소를 사용하는 것이 좋습니다. 제어되는 구성 요소에서 양식과 같은 데이터는 React 구성 요소 자체에서 처리됩니다. 제어되지 않는 구성요소는 양식의 데이터가 Dom 자체에 의해 제어된다는 것을 의미합니다. 다음은 일반적인 비제어 구성 요소입니다.
React의 경우 비제어 구성 요소의 상태 및 사용자 입력은 직접 제어할 수 없으며 상호 작용을 위해 양식 태그의 기본 기능에만 의존할 수 있습니다. 위 예제의 uncontrolled 컴포넌트를 control 컴포넌트로 변경하면 코드도 매우 간단해집니다.
class NameForm extends React.Component { state= {value: ''} handleChange = event => { this.setState({value: event.target.value}); } handleSubmit = event => { alert('A name was submitted: ' + this.state.value); event.preventDefault(); } render() { return (
이때 폼 값과 동작은 React 컴포넌트로 제어되므로 개발이 더욱 편리해집니다.
이것은 물론 매우 기본적인 개념입니다. 계속 읽어보세요.
위에 소개된 예는 제가 "narrow sense제어 및 비제어" 구성 요소라고 부르는 것입니다. 넓게 말하면 완전히 비제어 컴포넌트는 내부 상태를 포함하지 않고 props만 허용하는 기능적 컴포넌트 또는 상태 비저장 컴포넌트를 의미한다고 생각합니다. 렌더링 동작은 외부 소품에 의해 완전히 제어되며 자체적인 "자율성"이 없습니다. 이러한 구성요소는 재사용성이 뛰어나고 테스트 가능성이 뛰어납니다.
그러나 UI "휠" 디자인에서는 "반자율" 또는 "완전히 제어되지 않는"구성 요소가 때로는 더 나은 선택입니다. 우리는 이것을 "컨트롤 소품" 패턴이라고 부릅니다. 간단히 말하면: 구성 요소에는 자체 상태가 있습니다. 관련 prop이 전달되지 않으면 구성 요소가 호출될 때 자체 상태 상태가 렌더링 및 상호 작용 논리를 완료하는 데 사용됩니다. , 비즈니스 소비 수준에 따라 행동이 제어됩니다.
많은 커뮤니티 UI "바퀴"를 조사한 후 Kent C. Dodds가 작성하고 PayPal에서 사용하는 구성 요소 라이브러리인 downshift가 이 패턴을 널리 채택한다는 것을 발견했습니다.
단순히 Toogle 구성 요소를 예로 사용합니다. 이 구성 요소가 비즈니스 당사자에 의해 호출될 때:
class Example extends React.Component { state = {on: false, inputValue: 'off'} handleToggle = on => { this.setState({on, inputValue: on ? 'on' : 'off'}) } handleChange = ({target: {value}}) => { if (value === 'on') { this.setState({on: true}) } else if (value === 'off') { this.setState({on: false}) } this.setState({inputValue: value}) } render() { const {on} = this.state return ( <p> <input> <toggle></toggle> </p> ) } }
효과는 다음과 같습니다.
입력 상자를 통해 Toggle 구성 요소 상태 전환을 제어할 수 있습니다( 상태를 활성화하려면 "on"을 입력하고, "off"(상태가 회색으로 표시됨)를 입력하고 마우스로 클릭하여 전환할 수도 있습니다. 이때 입력 상자의 내용이 그에 따라 변경됩니다.
이 점을 생각해 보십시오. UI 구성 요소인 Toggle의 경우 비즈니스 호출자가 상태를 제어할 수 있으므로 사용 수준에서 소비 편의성을 제공합니다. 비즈니스 코드에서는 입력이든 다른 구성 요소이든 상태를 제어할 수 있으며 호출 시 완전한 제어 권한을 갖습니다.
동시에 Toggle 컴포넌트 호출 시 props 값을 전달하지 않으면 해당 컴포넌트는 정상적으로 작동할 수 있습니다.
<toggle> {({on, getTogglerProps}) => ( <p> <button>Toggle me</button> </p> <p>{on ? 'Toggled On' : 'Toggled Off'}</p> )} </toggle>
Toggle 컴포넌트는 전환 효과를 얻기 위해 상태 전환 시 내부 상태를 유지하는 동시에 render prop 모드를 통해 이 컴포넌트의 상태 정보를 외부로 출력합니다.
Toggle 소스 코드를 살펴보겠습니다(일부 링크는 삭제되었습니다):
const callAll = (...fns) => (...args) => fns.forEach(fn => fn && fn(...args)) class Toggle extends Component { static defaultProps = { defaultOn: false, onToggle: () => {}, } state = { on: this.getOn({on: this.props.defaultOn}), } getOn(state = this.state) { return this.isOnControlled() ? this.props.on : state.on } isOnControlled() { return this.props.on !== undefined } getTogglerStateAndHelpers() { return { on: this.getOn(), setOn: this.setOn, setOff: this.setOff, toggle: this.toggle, } } setOnState = (state = !this.getOn()) => { if (this.isOnControlled()) { this.props.onToggle(state, this.getTogglerStateAndHelpers()) } else { this.setState({on: state}, () => { this.props.onToggle( this.getOn(), this.getTogglerStateAndHelpers() ) }) } } setOn = this.setOnState.bind(this, true) setOff = this.setOnState.bind(this, false) toggle = this.setOnState.bind(this, undefined) render() { const renderProp = unwrapArray(this.props.children) return renderProp(this.getTogglerStateAndHelpers()) } } function unwrapArray(arg) { return Array.isArray(arg) ? arg[0] : arg } export default Toggle
关键的地方在于组件内 isOnControlled 方法判断是否有命名为 on 的属性传入:如果有,则使用 this.props.on 作为本组件状态,反之用自身 this.state.on 来管理状态。同时在 render 方法中,使用了 render prop 模式,关于这个模式本文不再探讨,感兴趣的读者可以在社区中找到很多资料,同时也可以在我新书中找到相关内容。
盘点一下,control props 模式反应了典型的控制权问题。这样的“半自治”能够完美适应业务需求,在组件设计上也更加灵活有效。
提到控制权话题,怎能少得了 Redux 这样的状态管理工具。Redux 的设计在方方面面都体现出来良好的控制权处理,这里我们把注意力集中在异步状态上,更多的内容还请读者关注我的新书。
Redux 处理异步,最为人熟知的就是 Redux-thunk 这样的中间件,它由 Dan 亲自编写,并在 Redux 官方文档上被安利。它与其他所有中间件一样,将 action 到 reducer 中间的过程进行掌控,使得业务使用时可以直接 dispatch 一个函数类型的 action,实现代码也很简单:
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); export default thunk;
但是很快就有人认为,这样的方案因为在中间件实现中的控制不足,导致了业务代码不够精简。我们还是需要遵循传统的 Redux 步骤:八股文似的编写 action,action creactor,reducer......于是,控制粒度更大的中间件方案应运而生。
Redux-promise 中间件控制了 action type,它限制业务方在 dispatch 异步 action 时,action的 payload 属性需要是一个 Promise 对象时,执行 resolve,该中间件触发一个类型相同的 action,并将 payload 设置为 promise 的 value,并设 action.status 属性为 "success"。
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload .then(result => dispatch({ ...action, payload: result })) .catch(error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); }) : next(action); }; }
这样的设计与 Redux-thunk 完全不同,它将 thunk 过程控制在中间件自身中,这样一来,第三方轮子做的事情更多,因此在业务调用时更加简练方便。我们只需要正常编写 action 即可:
dispatch({ type: GET_USER, payload: http.getUser(userId) // payload 为 promise 对象 })
我们对比一下 Redux-thunk,相对于“轮子”控制权较弱,业务方控制权更多的 Redux-thunk,实现上述三行代码,就得不得不需要:
dispatch( function(dispatch, getState) { dispatch({ type: GET_USERE, payload: userId }) http.getUser(id) .then(response => { dispatch({ type: GET_USER_SUCCESS, payload: response }) }) .catch(error => { dispatch({ type: GET_DATA_FAILED, payload: error }) }) } )
当然,Redux-promise 控制权越多,一方面带来了简练,但是另一方面,业务控制权越弱,也丧失了一定的自主性。比如如果想实现乐观更新(Optimistic updates),那就很难做了。具体详见 Issue #7
为了平衡这个矛盾,在 Redux-thunk 和 Redux-promise 这两个极端控制权理念的中间件之间,于是便存在了中间状态的中间件:Redux-promise-middleware,它与 Redux-thunk 类似,掌控粒度也类似,但是在 action 处理上更加温和和渐进,它会在适当的时机 dispatch XXX_PENDING、XXX_FULFILLED 、XXX_REJECTED 三种类型的 action,也就是说这个中间件在掌控更多逻辑的基础上,增加了和外界第三方的通信程度,不再是直接高冷地触发 XXX_FULFILLED 、XXX_REJECTED,请读者仔细体会其中不同。
了解了异步状态中的控制权问题,我们再从 Redux 全局角度进行分析。在内部分享时,我将基于 Redux 封装的状态管理类库共同特性总结为这一页 slide:
以上四点都是相关类库基于 Redux 所进行的简化,其中非常有意思的就是后面三点,它们无一例外地与控制权相关。以 Rematch 为代表,它不再是处理 action 到 reducer 的中间件,而是完全控制了 action creator,reducer 以及联通过程。
具体来看:
业务方不再需要显示申明 action type,它由类库直接函数名直接生成,如果 reducer 命名为 increment,那么 action.type 就是 increment;
同时控制 reducer 和 action creator 合二为一,态管理从未变得如此简单、高效。
我把这样的实践称为控制主义或者极简主义,相比 Redux-actions 这样的状态管理类库,这样的做法更加彻底、完善。具体思想可参考 Shawn McKay 的文章,介绍的比较充分,这里我不再赘述。
제어권은 궁극적으로 일종의 디자인 아이디어이며, 이는 타사 라이브러리와 비즈니스 소비 간의 대결과 충돌입니다. 이는 언어 및 프레임워크와 관련이 없습니다. 이 기사에서는 React를 예로만 사용합니다. 실제로 프로그래밍 분야의 모든 곳에서 제어에 대한 투쟁을 볼 수 있습니다. UI 추상화 및 상태 추상화 분석 각각 제어 권한 및 코더 밀접하게 관련되어 있어 프로그래밍 경험과 개발 효율성을 직접적으로 결정합니다.
하지만 프로그래밍 초기 단계에서는 뛰어난 컨트롤 디자인을 하루아침에 달성하기 어렵습니다. 일선 개발에 전념하고, 우리 자신의 비즈니스 요구 사항을 진정으로 이해하고, 수많은 모범 사례를 요약하고, 커뮤니티의 본질을 참조하고 뛰어난 오픈 소스 작업을 분석함으로써 우리 모두 성장할 것이라고 믿습니다.
관련 권장 사항:
react router4+redux를 사용하여 라우팅 권한을 제어하는 단계에 대한 자세한 설명
react router4+redux를 사용하여 라우팅 권한 제어를 구현하는 방법
위 내용은 React의 고급 설계 및 제어 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!