Open shoutingwei opened 6 years ago
createStore原理
export default function createStore(reducer, initialState) {
...
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
dispatch: 用于action的分发,改变store里面的state subscribe: 注册listener,store里面state发生改变后,执行该listener getState: 读取store里面的state replaceReducer: 替换reducer,改变state修改的逻辑
combineReducer
import { combineReducers, createStore } from 'redux'
let reducer = combineReducers({ visibilityFilter, todos })
let store = createStore(reducer)
connect
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { sendMessage, deleteMessage } from './actions'
class SomeComponent extends Component {
handleSend = message => {
### this.props.sendMessage(message)
}
handleDelete = id => {
### this.props.deleteMessage(id)
}
render() {
// ...
}
}
const mapStateToProps = state => state
// ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽ ▽
const mapDispatchToProps = /* this part is to be discussed */
// △ △ △ △ △ △ △ △ △ △
export default connect(
mapStateToProps,
mapDispatchToProps,//接受一个对象或者返回一个对象的函数,这个对象都是函数,函数名为key,值为函数
)(SomeComponent)
mapDispatchToProps 方法1 用函数直接绑定
const mapDispatchToProps = dispatch => ({
sendMessage: message => dispatch(sendMessage(message)),
deleteMessage: id => dispatch(deleteMessage(id)),
})
方法2 直接用对象
const mapDispatchToProps = {
sendMessage, // will be wrapped into a dispatch call
deleteMessage, // will be wrapped into a dispatch call
};
import * as messageActions from './messageActions'
import * as userActions from './userActions'
// ...
const mapDispatchToProps = {
...messageActions,
...userActions,
};
方法3 bindActionCreators
import { bindActionCreators } from 'redux'
// ...
const mapDispatchToProps = dispatch => bindActionCreators(
{
sendMessage,
deleteMessage,
},
dispatch,
)
bindActionCreators原理
export default function bindActionCreators(actionCreators, dispatch) {//返回多个函数构成的对象
if (typeof actionCreators === 'function') { // #1
return bindActionCreator(actionCreators, dispatch) // #2 单个函数
}
....
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') { // #3
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) //多个函数
}
}
return boundActionCreators
}
function bindActionCreator(actionCreator, dispatch) { //高阶函数 HOC 返回函数
return (...args) => dispatch(actionCreator(...args))
}
//如果不用connect 直接调用
const MyNewActionCreators = bindActionCreators(actionCreator, dispatch);
MyNewActionCreators.increment() // { type: 'INCREMENT', step: 1 }
MyNewActionCreators.increment(2) // { type: 'INCREMENT', step: 2 }
MyNewActionCreators.increment(3) // { type: 'INCREMENT', step: 3 }
MyNewActionCreators.decrement() // { type: 'DECREMENT', step: -1 }
MyNewActionCreators.decrement(2) // { type: 'DECREMENT', step: -2 }
MyNewActionCreators.decrement(3) // { type: 'DECREMENT', step: -3 }
//如果不用bindActionCreators 直接调用
dispatch(MyActionCreators.increment()) // { type: 'INCREMENT', step: 1 }
dispatch(MyActionCreators.increment(2)) // { type: 'INCREMENT', step: 2 }
dispatch(MyActionCreators.increment(3)) // { type: 'INCREMENT', step: 3 }
dispatch(MyActionCreators.decrement()) // { type: 'DECREMENT', step: -1 }
dispatch(MyActionCreators.decrement(2)) // { type: 'DECREMENT', step: -2 }
dispatch(MyActionCreators.decrement(3)) // { type: 'DECREMENT', step: -3 }
import { bindActionCreators } from 'redux'
// ...
const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
sendMessage,
deleteMessage,
},
dispatch,
),//返回对象 并展开
otherService, // this is not to be wrapped into dispatch
})
代码#1的判断语句是为了做兼容处理,当接收的参数actionCreators为一个函数的时候,则认为它是单一的动作工厂,便在代码#2处直接调用bindActionCreator工具函数来封装调度过程。
另一情况是当actionCreators参数是一个对象的时候,则遍历这个对象。代码#3会判断每个键在原始对象中的值是否是个函数,如果是一个函数则认为它是一个动作工厂,并使用bindActionCreator函数来封装调度过程,最后把生成的新函数以同样的键key存储到boundActionCreators对象中。 在函数的末尾会返回boundActionCreators对象。
actionCreator
const counterIncActionCreator = function(step) {
return {
type: 'INCREMENT',
step: step || 1
}
}
mapStateToProps(store.getState(), ownProps)
const mapStateToProps = (state) => {
return {
counter: state.counter
}
};
const connect = (mapStateToProps) => (SomeComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { allProps: {} }
}
componentWillMount () {
const { store } = this.context
this.setProps()
}
setProps () {
const { store } = this.context
//this.props 为什么this能获取到SomeComponent上的props
**let stateProps = mapStateToProps(store.getState(), this.props) // SomeComponent上额外传入 props**
this.setState({
allProps: { // 整合普通的 props 和从 state 生成的 props
...stateProps,
...this.props
}
})
}
render () {
return <SomeComponent {...this.state.allProps} />
}
}
**return Connect**
}
mapStateToProps, mapDispatchToProps
const connect = (mapStateToProps, mapDispatchToProps) => (SomeComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this.setProps()
store.subscribe(() => this.setProps()) //一旦变化, props更新
}
setProps () { // 做了一下完整性的判断
const { store } = this.context
let stateProps = mapStateToProps
**? mapStateToProps(store.getState(), this.props)**
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
**? mapDispatchToProps(store.dispatch, this.props)**
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
**...stateProps,
...dispatchProps,**
...this.props
}
})
}
render () {
return <SomeComponent {...this.state.allProps} />
}
}
return Connect
}
connect中第三个参数 [mergeProps(stateProps, dispatchProps, ownProps): props] (Function):
如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。
该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,
默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
mergeProps还是一个函数,用来筛选哪些参数传递给组件。这个函数接受3个参数。
const defaultMergeProps = (stateProps, dispatchProps, ownProps) => ({
...parentProps,
...stateProps,
...dispatchProps
})
connect的第四个参数 [options] (Object) 如果指定这个参数,可以定制 connector 的行为。
options,这个是一个对象,有两个布尔,一个是pure,一个是withRef。 如果pure为false,只要connect接受到属性,不管是否有变化,必然刷新connect组件。 withRef为true时,在装饰传入的 React 组件时,Connect 会保存一个对该组件的 refs 引用,可以通过 getWrappedInstance 方法来获得该 refs,并最终获得原始的 DOM 节点。
Provider
provider用来给react组件提供数据,数据放在context中的store键。源码非常简单。
const App = () => {
return (
<Provider store={store}>
<Comp/>
</Provider>
)
};
function createProvider(storeKey = 'store', subKey) {
const subscriptionKey = subKey || `${storeKey}Subscription`//subscriptionKey有啥用??
class Provider extends Component {
// 拿到传入的store直接挂在到当前store上
**constructor(props, context) {**
super(props, context)
this[storeKey] = props.store;//为啥不是props[storeKey]
}
// getChildContext: 将store传递给子孙component
getChildContext() {
return { [storeKey]: this[storeKey], [subscriptionKey]: null }
}
render() {
//this.props.children 是Provider内的子元素,Children.only用来Verifies that children has only one child //(a React element) and returns it. Otherwise this method throws an error.
return Children.only(this.props.children);
}
}
if (process.env.NODE_ENV !== 'production') {
**Provider.prototype.componentWillReceiveProps = function (nextProps) {**
// 如果store有改变,则发出警告
if (this[storeKey] !== nextProps.store) {//store引用是不会变的
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired,
}
Provider.childContextTypes = {
[storeKey]: storeShape.isRequired,
[subscriptionKey]: subscriptionShape,
}
Provider.displayName = 'Provider'
return Provider
}
我们看到,仅仅是把传入的store放在this[storeKey],又在getChildContext中把这个store返回给子组件而已。这里为了可扩展,storeKey当成参数(默认值store)传递进去。此外,子组件还能在context中获得另外一个值:key = storeSubscription ,值为null的属性。目前并没有什么卵用。
另外,每次改变store属性时候,都会报出一个警告。第二次改变时候不会警告,直接退出。也就是说Provider不支持动态修改store。
首先,在父组件中,定义两个属性:
**函数getChildContext,它定义了开放给子节点的context。
属性childContextTypes,它定义了getChildContext返回的对象类型。**
Context
首先,在父组件中,定义两个属性:
函数getChildContext,它定义了开放给子节点的context。 属性childContextTypes,它定义了getChildContext返回的对象类型。
class ParentComponent extends React.Component {
getChildContext() {
return { foo: 'bar' }
}
render() {
return <ChildComponent />
}
}
ParentComponent.childContextTypes = {
foo: React.PropTypes.string
}
ChildComponent 现在可以通过定义属性 contextTypes来获取context中的foo了: 在一个函数式,无状态的组件中,context通过第二个函数参数传入。在标准的用class定义的组件中,可以使用this.context定义context。
**const ChildComponent = (props, context) => {**
return <p>The value of foo is: { context.foo }</p>
}
ChildComponent.contextTypes = {
foo: React.PropTypes.string
}
如果你是开源库的作者,那么context是很有用的。比如说 React Router中的组件就使用了context。当你在写一个库的时候,组件之间如果需用通信或互传数据,context是完美的选择。另外一个使用了context的著名库是react-redux。
Router
const { Component, PropTypes } = React
class Router extends Component {
getChildContext() {
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Method_definitions
const router = { register(url) { console.log('registered route!', url) } }//注意函数方式
return { router: router }
}
render() { return <div>{this.props.children}</div> }
}
Router.childContextTypes = {
router: PropTypes.object.isRequired,
}
Route
class Route extends Component {
componentWillMount() {
this.context.router.register(this.props.path)
}
render() {
return <p>I am the route for {this.props.path}</p>
}
}
Route.contextTypes = {
router: PropTypes.object.isRequired,
}
示例
const App = () => (
<div>
<Router>
<div>
<Route path="/foo" />
<Route path="/bar" />
<div>
<Route path="/baz" />
</div>
</div>
</Router>
</div>
)
npm install --save redux npm install --save react-redux npm install --save-dev redux-devtools
你应该把要做的修改变成一个普通对象,这个对象被叫做 action,而不是直接修改 state。然后编写专门的函数来决定每个 action 如何改变应用的 state,这个函数被叫做 reducer。
如果你以前使用 Flux,那么你只需要注意一个重要的区别。Redux 没有 Dispatcher 且不支持多个 store。相反,只有一个单一的 store 和一个根级的 reduce 函数(reducer)。随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
唯一的stroe,但可以有多个reducer。