参考资料
Awesome Redux包含文档,redux项目,以及一些脚手架工具
慕课网
example
the-soundcloud-client-in-react-redux
中间件middleware
参考资料:
example
观看example的时候,要把整个脉络action--》reducer---》store---》搞清楚,比如reducer要书写,参考(action--》reducer这条线)所以需要action这个参数入侵。比如store要书写,需要reducer--》store,所以需要reducer入侵。
代码的编写参考例子。
故障分析
有时候cnpm不起作用,可能是被360拦截了。
添加信任就可以了。
官网
Counter Vanilla
一个入门demo,描述了redux如何运作的过程,从action--》reducer---》store---》更新应用state的过程。
- store:
- store.dispatch({ type: 'xxx' }),这里store分发dispatch(触发)这些action,回调subscribe由store订阅 store.subscribe(render)好了
- reducer:
- fn:counter(state,action)是reducer,根据action.type,得到不同的状态。
- var store = Redux.createStore(counter),这里store就和reducer绑定在一起了。
- action
- { type: 'xxx' }描述一种行为。行为具体怎么发生在reducer(这里是counter函数)里面定义了。
注意点:redux采用的是消费订阅者模式。
Counter
commonjs方式的demo。简易版的demo,方便了解整个rudux过程。
Redux 结合 React 使用的最基本示例。出于简化,当 store 发生变化,React 组件会手动重新渲染。在实际的项目中,可以使用 React 和 Redux 已绑定、且更高效的React Redux。
要点
- reactjs组件不需要知道redux,只是在外层导入props的时候
onIncrement={() => store.dispatch({type:'INCREMENT'})}
关联store,让store去dispatch相应的动作,根据subscribe到store的reducer去判断类型,进行操作。这也是redux的底层的操作。 - 组件和redux相关的组件书写的时候不会相互影响,可以分开编写,测试。
- couter组件不包含状态,可以使用另一种写法,不使用class的方式
import React,{Component} from 'react' /*class Counter extends Component { }*/ const incrementIfOdd = (value,onIncrement) => { if (value % 2 !== 0) { onIncrement() } } const incrementAsync = (onIncrement) => { setTimeout(onIncrement,1000) } export default ({value,onIncrement,onDecrement})=>{ return ( <p> Clicked: {value} times <button onClick={onIncrement}> + </button> <button onClick={onDecrement}> - </button> <button onClick={e=>{ incrementIfOdd(value,onIncrement) } }> incrementIfOdd </button> <button onClick={ e=>{ incrementAsync(onIncrement) } }> incrementAsync </button> </p> ) }
注意 onClick里面不能直接{incrementifOdd(value,onIncrement)}
问题:
- package.json文件"react-scripts": "^0.6.0",这个文件的作用(好像是简化测试,以及发布的模块),配合这个库使用的要加入react-addons-test-utils,enzyme这两个模块。
Todos
深入理解在 Redux 中 state 的更新与组件是如何共同运作的例子。展示了 reducer 如何委派 action 给其它 reducer,也展示了如何使用React Redux从展示组件中生成容器组件。
要点
- container的都是要跟redux关联的组件,也就是容器组件的作用,从redux的state树中取得自己要的数据mapStateToProps和动作mapDispatchToProps(要响应的dispath),关联react展示组件。然后react组件从这些容器中取得相应的数据和动作进行处理。
- 组件触发动作action(带有数据)的时候,redux会去遍历所有的reduce,然后找到匹配的reduce,然后redux会自动更新state,react会更新视图。
- 容器组件可以用动词来表达,比如addTodo,VisibleTodoList,而展示组件可以是一个名词比如footer等。
- 注意容器组件AddTodo的写法。没有写mapStateToProps,这些东西。参考connect用法
import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' let AddTodo = ({ dispatch }) => { let input return ( <div> <form onSubmit={e => { e.preventDefault() if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' }}> <input ref={node => { input = node }} /> <button type="submit"> Add Todo </button> </form> </div> ) } AddTodo = connect()(AddTodo) export default AddTodo
比较这两个写法:具体看api使用:FilterLink.js里的定义是一个函数,VisibleTodoList里的定义是一个对象。
比较FilterLink.js和VisibileTodoList.js的mapDispatchToProps写法,注意两者的区别FilterLink是属性在标签里定义了。所以:
-
const mapDispatchToProps = (dispatch,ownProps) => ({ onClick:()=>{ dispatch(setVisibilityFilter(ownProps.filter)) } })
而VisibleTodoList的是从内部去跑这个方法的。
const mapDispatchToProps = { onTodoClick: toggleTodo }
另外,参考undo里面有一个写法:这也是传入id来处理的。
const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { dispatch(toggleTodo(id)) } } }
参数传递在Todo里面
const todoList = ({todos,onTodoClick})=>{ return (<ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={()=> onTodoClick(todo.id)} /> )} </ul>) }
todomvc
要点:
- 改写官网TodoTextInput.js源文件如下:组件继承方式
import React,{ Component,PropTypes } from 'react' import classnames from 'classnames' export default class TodoTextInput extends Component { static propTypes = { onSave: PropTypes.func.isrequired,text: PropTypes.string,placeholder: PropTypes.string,editing: PropTypes.bool,newTodo: PropTypes.bool } state = { text: this.props.text || '' } handleSubmit = e => { const text = e.target.value.trim() if (e.which === 13) { this.props.onSave(text) if (this.props.newTodo) { this.setState({ text: '' }) } } } handleChange = e => { this.setState({ text: e.target.value }) } handleBlur = e => { if (!this.props.newTodo) { this.props.onSave(e.target.value) } } render() { return ( <input className={ classnames({ edit: this.props.editing,'new-todo': this.props.newTodo })} type="text" placeholder={this.props.placeholder} autoFocus="true" value={this.state.text} onBlur={this.handleBlur} onChange={this.handleChange} onKeyDown={this.handleSubmit} /> ) } }
这种写法,内部保持一个state,需要state一般是需要和用户进行交互,改写onKeyDown源文件如下函数组件:
import React from 'react' import classnames from 'classnames' /*const handleSubmit =(e)=>{ const text = e.target.value.trim() if(e.which === 13) { } } const handleBlur = (e)=>{ console.log(this.props) }*/ const TodoTextInput =({...props})=> <input type="text" className={ classnames({ edit: props.editing,'new-todo': props.newTodo })} autoFocus="true" /* onBlur={handleBlur}*/ onKeyDown={(e)=>{ const text = e.target.value.trim() if(e.which === 13&&text!=='') { props.onSave(text) e.target.value="" } }} placeholder={props.placeholder}/> export default TodoTextInput
注意这里onKeyDown里面要引用外部的props,所以不能抽取到外面handleSubmit方法。会报props找不到。换个思路,如果使用链接工厂的方式:
onBlur={handleBlur(props)} ----------------------------------------------------- const handleBlur = (props)=>(e)=>{ //链接工厂的方式 console.log(props,e,e.target.value) }
这样也是可以的。或者换个思路
onBlur={e=>{handleBlur(props,e)}} ----------------------------------------- const handleBlur = (props,e)=>{ console.log(props,e.target.value) }
-
函数调用。一般下面的方法,如果只写一个textxxx那么textxxx都可以调用的到,如果这样写了,根据就近原则,内部函数先调用了。
-
import React from 'react' import TodoTextInput from './TodoTextInput' const textsss = ()=>{ console.log("xx") } const Header = ({addTodo}) => { const textsss = ()=>{ console.log("xxwww") } //这里如何定义内部方法。 return (<header className="header"> <h1>todos</h1> <TodoTextInput newTodo onSave={(text)=>{ textsss() addTodo(text) }} placeholder="what need to be done?" /> </header>) } export default Header
- 注意看下面的定义对象的方式 mainsection里面:
const TODO_FILTERS = { [SHOW_ALL]: () => true,[SHOW_ACTIVE]: todo => !todo.completed,[SHOW_COMPLETED]: todo => todo.completed }
这里[]:xxx,[]里面的是一个变量。
-
reduces/todos.js 里面的complete里面的一个写法:对象扩展运算符,...todo,就是把对象扩展了。然后后面的属性覆盖前面的。
case types.COMPLETE_TODO: return state.map(todo=>{ return todo.id === action.id? {...todo,completed:!todo.completed}: todo })
async
可以参考这篇文章。
- 注意代码段,
export const fetchPostsIfNeeded = reddit => (dispatch,getState) => { debugger if (shouldFetchPosts(getState(),reddit)) { return dispatch(fetchPosts(reddit)) } }
这里getState是从哪里注入进来的?注意这里调用方是
componentDidMount() { const { dispatch,selectedReddit } = this.props dispatch(fetchPostsIfNeeded(selectedReddit)) }
dispatch(fetchPostsIfNeeded(selectedReddit))
就是说只有注入dispatch,触发dispatch,才有getState()这个变量。让我们可以做下一步的操作。可以换成下面的代码块。export const fetchPostsIfNeeded = reddit =>{ return (dispatch,getState) => { if (shouldFetchPosts(getState(),reddit)) { return dispatch(fetchPosts(reddit)) } } }
dispatch(fetchPostsIfNeeded(selectedReddit))
调用的时候,最后返回的是一个函数。可以有这两个变量。我们修改todomvc 例子里面的action/index.js,加入dispatch,getState(),如下,注意,如果想改成这样的话,export const addTodo = text =>(dispatch,getState)=> {debugger;return ({ type: types.ADD_TODO,text })}
必须加入中间件。发现这是系统调用的时候调用的时候自动加入的。这也可以理解,actioncreator本来是要返回一个plain的obj,不过现在需要做其他的操作(网络请求),就需要中间件。按照正常的逻辑,走网络请求得到数据应该要走网络请求,所以函数的参数,就是状态(getState)和动作(dispatch)。继续做下一步的处理
-
使用thunk中间件是因为actionCreator不仅仅要返回一个plain对象,还要在函数里面做一些操作。比如异步请求,然后最后返回的是一个函数。
当 action creator 返回函数时,这个函数会被 Redux Thunk middleware 执行。这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。这个函数还可以 dispatch action,就像 dispatch 前面定义的同步 action 一样。
shopping
比较这两种写法:
productContainer
<ProductsList title="Products"> {products.map(product => <ProductItem key={product.id} product={product} onAddToCartClicked={() => addToCart(product.id)} /> )} </ProductsList>
productItem里面:
<button onClick={onAddToCartClicked} disabled={product.inventory > 0 ? '' : 'disabled'}> {product.inventory > 0 ? 'Add to cart' : 'Sold Out'} </button>
这种写法把点击事件的具体操作放到外层容器处理。
另一种写法是:
productContainer
<ProductsList title={"shoppingcart"}> {products.map(product=><ProductItem key={product.id} product={product} addToCart={addToCart}/>)} </ProductsList>
productItem里面做处理:
export default ({product,addToCart}) => ( <div> <Product title={product.title} price={product.price} /> <button onClick={()=>addToCart(product.id)} disabled={product.inventory > 0 ? '' : 'disabled'}> {product.inventory > 0 ? 'Add to cart' : 'Sold Out'} </button> </div>)
在productItem里面onClick另外处理,处理的场景不一样而已。主要是看业务需要分割到什么样的程度。
注意以下这两种写法:
actions.js
export const testConsoleProductUnderirect = ()=>dispatch=>{ dispatch({ type:types.TEST_CONSOLE_PRODUCTS }) } export const testConsoleProductInderirect = ()=>({ type:types.TEST_CONSOLE_PRODUCTS })
调用方式为
store.dispatch(testConsoleProductUnderirect())
store.dispatch(testConsoleProductInderirect())
第二种是常规的写法,就是返回一个普通的action对象。第一种写法是要由thunk中间件来触发的写法。注意
const xx=()=>dispatch=>({XX:XX})
每一层 箭头都是有()包裹,是直接返回的。第一个xx=()=>dispatch
说明是返回一个参数为dispatch的函数,一般actionCreator是要返回action对象的,这里返回了一个函数,需要用到thunk支持,第二层箭头>dispatch=>{ dispatch({ type:types.TEST_CONSOLE_PRODUCTS }) }
里面就可以dispatch其他操作。根据中间件文档的解释,中间件处理过程如下。传入action,然后返回next----》dispatch这个action,然后再store
这个next
store => next => action
层层嵌套。
注意使用了中间件之后,第二种actionCreator可以返回任意东西。只要有dispatch参数进去就可以了。
export const testConsoleProductUnderirect = ()=>dispatch=>{ console.log("hello kitty") /*dispatch({ type:types.TEST_CONSOLE_PRODUCTS })*/ }
第一种同步actionCreator不行
export const testConsoleProductInderirect = ()=>{ console.log("hello kitty") }
这样写是会报错的。