리덕스 소개
이 글은 모든 사람이 리액트와 플럭스 아키텍처에 대해 어느 정도 지식이 있고, 리덕스를 사용했거나 이해했다는 가정을 전제로 하므로 가장 기본적인 것부터 시작하지 않고 직접 리덕스를 다루겠습니다. 요약합니다. Redux를 사용해 본 적이 없다면 먼저 여기를 읽어보는 것이 가장 좋습니다
redux를 이해하려면 먼저 redux의 설계 원칙 중 일부를 요약해야 합니다.
단일 데이터 source
Redux는 전체 애플리케이션의 상태를 저장하기 위해 단일 객체 트리 구조만을 사용하는데, 이는 전체 애플리케이션에서 사용될 데이터인 스토어(storage)라고 합니다. 스토어는 저장된 데이터 외에도 전체 애플리케이션의 상태(추후 소개할 라우터 상태 포함)도 저장할 수 있으므로 스토어를 통해 즉시 저장 기능(스냅샷 생성) 구현이 가능해집니다. ) 또한 이 디자인은 서버 측 렌더링 가능성도 제공합니다.
상태는 읽기 전용입니다
플럭스의 디자인 컨셉에 부합하기 때문에 매장 상태를 변경할 수 없습니다. 구성 요소(실제로 Redux는 리듀서를 기반으로 저장소를 생성하지만) 디스패치를 통해 현재 상태를 반복하는 작업만 트리거할 수 있습니다. 여기서는 애플리케이션 상태를 직접 수정하지 않고 완전히 새로운 상태를 반환합니다.
상태 수정은 순수 함수로 구성됩니다
Redux의 리듀서 프로토타입은 다음과 같습니다. 이전 상태 + 동작 = 새 상태의 공식입니다:
(previousState, action) => newState
각 리듀서는 순수 함수이므로 부작용이 없습니다. 이 디자인의 이점은 리듀서를 사용하여 수정한다는 것뿐만이 아닙니다. state 간단하고 순수하게 테스트할 수 있습니다. 또한 redux는 각 반환 상태를 저장하여 쉽게 시간 이동을 생성하고 작업으로 인한 각 변경 결과를 추적할 수 있습니다.
React에서 Redux를 사용하려면 React-Redux와 Redux가 모두 필요합니다.
이 부분은 주로 제가 이해한 부분을 다루고 있습니다. 다소 추상적일 수도 있고 완전히 정확하지 않을 수도 있으니 바로 건너뛰셔도 됩니다.
redux의 핵심 메소드는 createStore입니다. React의 핵심 기능은 모두 createStore와 최종 생성된 저장소에 포함되어 있습니다. createStore 메소드 자체는 세 가지 매개변수(reducer,initialState) 전달을 지원합니다. , Enhancer , Enhancer 는 강화된 패키징 기능으로 사용될 수 있는데, 이는 우리가 흔히 사용하지 않는 기능입니다.
이 함수는 내부적으로 currentState를 유지하며 이 currentState는 getState 함수(내장)를 통해 반환될 수 있습니다. 또한 실제로 게시-구독 모드를 구현하고 store.subscribe를 통해 이벤트를 구독합니다. React-redux에 의해 수행되는 이 작업은 디스패치가 있을 때 모든 리스너를 트리거하고 전체 상태 트리를 업데이트하는 암시적인 작업을 수행하는 데 도움이 됩니다. 또한 내장된 디스패치 함수는 일련의 검증을 거친 후 리듀서를 트리거한 후 상태가 변경된 다음 리스너를 순차적으로 호출하여 전체 상태 트리의 업데이트를 완료합니다.
redux를 사용해 본 친구들은 실제로 redux-thunk와 같은 미들웨어가 낯설지 않습니다. 실제로 Redux도 middleWare를 잘 지원하고 있습니다. 이 개념은 nodejs의 미들웨어 메커니즘과 다소 유사하다고 생각됩니다. 작업은 각 middleWare를 차례로 통과한 후 다음 작업으로 전달됩니다. 각 middleWare는 최종 처리 기능이 완료될 때까지 작업을 중단하거나 변경하는 등의 다른 작업도 수행할 수 있습니다. 감속기에 넘겨졌습니다.
redux의 applyMiddleware 기능은 매우 정교합니다.
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) //注意这里的dispatch并不是一开始的store.dispatch,实际上是变化了的 } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
핵심은 dispatch = compose(...chain)(store.dispatch)입니다. 이 문장은 각 미들웨어에 대한 체인 호출입니다. compose의 소스 코드:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) }
는 이전 함수의 실행 결과를 다음 함수로 호출합니다.
사실 미들웨어 작성 과정은 매우 간단합니다. 예를 들어 redux-trunk는 실제로 다음과 같이 구성됩니다.
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
물론 우리는 먼저 React Tool Set의 React-Router는 반드시 Redux와 함께 사용할 필요는 없다고 명시되어 있는데, Redux에는 React-Router와 Redux와 함께 사용할 수 있는 또 다른 React-Router-Redux가 있는데 효과가 아주 좋습니다. .
이 부분에서는 React-Router 사용법을 소개하지 않기 때문에 React-Router 사용법은 중국어 문서를 참고하시기 바랍니다.
개발자는 JSX 태그를 통해 경로를 선언할 수 있으므로 경로를 작성하기 매우 친숙해지고 선언적 라우팅의 표현 능력이 상대적으로 높습니다. 강한.
중첩 경로 및 경로 일치: 지정된 경로에서 매개변수를 전달할 수 있습니다.
<Route path="/mail/:mailId" component={Mail} />
또한 매개변수가 선택사항인 경우 그냥 괄호로 묶으세요(:선택적 매개변수).
여러 경로 전환 방법 지원: 현재 경로 전환 방법은 hashchange 및 pushState를 사용하는 것에 불과하다는 것을 알고 있습니다. 전자는 브라우저 호환성이 더 좋지만 실제는 아닙니다. URL은 우아한 URL 경험을 제공하지만 서버가 경로 새로 고침 문제를 해결해야 합니다(서버가 자동으로 홈페이지로 리디렉션되어야 함).
简单的说,react-router-redux让我们可以把路由也当作状态的一部分,并且可以使用redux的方式改变路由:直接调用dispatch:this.props.push(“/detail/”);,这样把路由也当作一个全局状态,路由状态也是应用状态的一部分,这样可能更有利于前端状态管理。
react-router-redux是需要配合react-router来使用的,并不能单独使用,在原本的项目中添加上react-router-redux也不复杂:
import { createStore, combineReducers, compose, applyMiddleware } from 'redux'; import { routerReducer, routerMiddleware } from 'react-router-redux'; import { hashHistory } from 'react-router'; import ThunkMiddleware from 'redux-thunk'; import rootReducer from './reducers'; import DevTools from './DevTools'; const finalCreateStore = compose( applyMiddleware(ThunkMiddleware,routerMiddleware(hashHistory)), DevTools.instrument() )(createStore); console.log("rootReducer",rootReducer); const reducer = combineReducers({ rootReducer, routing: routerReducer, }); export default function configureStore(initialState) { const store = finalCreateStore(reducer, initialState); return store; }
另外,上文提到的demoreact-router-redux-demo用了react-router和react-router-redux,当然也用到了redux的一些别的比较好的工作,比如redux-devtools,有兴趣的朋友可以点击这里
这一部分讲述的是一种组件书写规范,并不是一些库或者架构,这些规范有利于我们在复杂的项目中组织页面,不至于混乱。
从布局的角度看,redux强调了三种不同的布局组件,Layouts,Views,Components:
Layouts: 指的是页面布局组件,描述了页面的基本结构,可以是无状态函数,一般就直接设置在最外层router的component参数中,并不承担和redux直接交互的功能。比如我项目中的Layouts组件:
const Frame = (props) => <p className="frame"> <p className="header"> <Nav /> </p> <p className="container"> {props.children} </p> </p>;
Views组件,我认为这个组件是Components的高阶组件或者Components group,这一层是可以和redux进行交互并且处理数据的,我们可以将一个整体性功能的组件组放在一个Views下面(注:由于我给出的demo十分简单,因此Views层和Components层分的不是那么开)
Components组件,这是末级渲染组件,一般来说,这一层级的组件的数据通过props传入,不直接和redux单向数据流产生交互,可以是木偶般的无状态组件,或者是包含自身少量交互和状态的组件,这一层级的组件可以被大量复用。
总而言之,遵守这套规范并不是强制性的,但是项目一旦稍微复杂一些,这样做的好处就可以充分彰显出来。
redux的单向数据流相对于双向数据绑定,在处理表单等问题上的确有点力不从心,但是幸运的是已经开源了有几个比较不错的插件:
redux-form-utils,好吧,这个插件的star数目非常少,但是他比较简单,源代码也比较短,只有200多行,所以这是一个值得我们看源码学习的插件(它的源码结构也非常简单,就是先定一个一个高阶组件,这个高阶组件可以给我们自己定义的表单组件传入新的props,定制组件,后一部分就是定义了一些action和reducer,负责在内容变化的时候通知改变状态树),但是缺憾就是这个插件没有对表单验证做工作,所以如果我们需要表单验证,还是需要自己做一些工作的。
另外还有一地方,这个插件源代码写法中用到了::这种ES6的语法,这其实是一种在es6中class内部,使用babel-preset-stage-0即可使用的语法糖:::this.[functionName] 等价于 this.[functionName].bind(this, args?)
redux-form,这个插件功能复杂,代码完善,体量也非常庞大,可以参考文档进行使用,但是读懂源代码就是比较麻烦的事情了。不过这个插件需要在redux的应用的state下挂载一个节点,这个节点是不需要开发者自己来操控的,他唯一需要做的事情就是写一个submit函数即可。我在自己的demo中也把一个例子稍加改动搬了过来,感觉用起来比较舒服。
想要做到redux性能优化,我们首先就要知道redux的性能可能会在哪些地方受到影响,否则没有目标是没有办法性能优化的。
因为我也不是使用redux的老手,所以也并不能覆盖所有性能优化的点,我总结两点:
有的时候,我们需要的数据格式会自带冗余,可以抽取出一些公共的部分从而缩减大小,比如我们需要的数据格式可能是这样的:
[ { name:"Nike", title:"国家一级运动员","国家一级裁判员" } { name:"Jenny", title:"国家一级裁判员" } { name:"Mark", title:"国家一级运动员" } ]
这个时候实际上我们可以优化成这样:
[ { "国家一级运动员":"Nike","Mark" "国家一级裁判员":"Jenny","Nike" } ]
这个时候,我们可以直接把后者当作store的格式,而我们用reselect这个库再转变成我们所要的格式,关于reselect怎么用上述链接有更详细的例子,在这里我就不过多介绍了。
事实上,对于redux来说,每当store发生改变的时候,所有的connect都会重新计算,在一个大型应用中,浪费的时间可想而知,为了减少性能浪费,我们可以对connect中的selector做缓存。
위에서 언급한 reselect 라이브러리에는 자체 캐싱 기능이 있습니다. 여기서는 매개변수를 비교하여 캐싱 사용 여부를 결정할 수 있습니다.
reselect의 캐싱 기능은 사용자가 맞춤 설정할 수 있습니다
redux 기술 스택과 관련된 더 자세한 기사를 보려면 PHP 중국어 웹사이트를 참고하세요. !