该文为阅读笔记,建议大家去看阮一峰老师的原文,若有侵权请联系删除
UI组件和容器组件
- UI组件
- 特点
- 只负责展示UI,不带业务逻辑
- 没有状态(即
不使用this.state这个变量) - 所有数据都由参数(
this.props)提供 不使用任何 Redux 的API
- 例子
class One extends Component { render() { let { n,dec,inc } = this.props return ( <div> <button onClick={dec}>-</button> {n} <button onClick={inc}>+</button> </div> ) } } - 特点
- 容器组件
- 特点
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
- 例子
以上例子来源class OneContainer extends Component { constructor(props) { super(props) this.state = { n: store.getState().n } //参数是一个函数 store.subscribe(this.change) } //当store里数据修改的时候会执行这个回调函数 change = () => { this.setState({ n: store.getState().n }) } dec() { store.dispatch(actionCreator.decAction(1)) } inc() { store.dispatch(actionCreator.incAction(2)) } render(){ return <One n={this.state.n} inc={this.inc} dec={this.dec} /> } } - 特点
- 小结:
UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑- 将二者分离的原因:
- UI组件一般都可以写成无状态组件。无状态组件性能比较高,它就是一个函数。而普通子组件,会有一些生命周期函数。当一个组件只负责页面的渲染,没有任何逻辑的时候就可以将这个组件写成一个性能较高的无状态组件。
connect()
- 作用
从 UI 组件生成容器组件,即将两类组件 (UI组件,容器组件) 连接起来
- 问题思考:
- 输入逻辑:外部的数据(即
state对象)如何转换为 UI 组件的参数; - 输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。
- 输入逻辑:外部的数据(即
- connect方法的完整API
两个参数定义UI组件的业务逻辑,(详见后文)import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList)- mapStateToProps
- 作用:建立一个从(外部的)
state对象到(UI 组件的)props对象的映射关系。负责输入逻辑,即将state映射到 UI 组件的参数(props) - 何时引发UI渲染?
-
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。 -
mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。 -
使用
ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染(暂时没有看懂例子)。
// 容器组件的代码 // <FilterLink filter="SHOW_ALL"> // All // </FilterLink> const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } }connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
-
- 作用:建立一个从(外部的)
- mapDispatchToProps
- 作用:用来建立 UI 组件的参数到
store.dispatch方法的映射。后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。即定义了哪些用户的操作应该当作 Action,传给 Store。 - 如果
mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。
从上面代码可以看到,const mapDispatchToProps = ( dispatch, ownProps ) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。 - 作用:用来建立 UI 组件的参数到
- mapStateToProps
connect 将当前组件和 state,action 联系起来,使得可以在组件中调用action中的方法和state中的数据 该段来源
- @connect Decorator - 装饰器(文章来源)
- 装饰器(Decorator)是一个函数,用来修改类的行为。
//定义一个函数,也就是定义一个Decorator,target参数就是传进来的Class。 //这里是为类添加了一个静态属性 function testable(target) { target.isTestable = true; } //在Decorator后面跟着Class,Decorator是函数的话,怎么不是testable(MyTestableClass)这样写呢? //我只能这样理解:因为语法就这样,只要Decorator后面是Class,默认就已经把Class当成参数隐形传进Decorator了。 @testable class MyTestableClass {} console.log(MyTestableClass.isTestable) // true- 不用装饰器和使用装饰器的区别
- 不用装饰器
import { connect } from 'react-redux'; class MyApp extends React.Component { // ...define your main app here } export default connect(mapStateToProps, mapDispatchToProps)(MyApp);- 使用装饰器
import { connect } from 'react-redux'; @connect(mapStateToProps, mapDispatchToProps) export default class MyApp extends React.Component { // ...define your main app here }-
使用装饰器需要的步骤
- 第一步需要安装npm依赖
npm install --save babel-plugin-transform-decorators-legacy- 第二步需要在package.json文件中配置一下
"plugins": [\ ["@babel/plugin-proposal-decorators", { "legacy": true }],\ ]
< Provider > 组件
connect方法存在的不足:使用该方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。- 低效的解决方案:将
state对象作为参数,传入容器组件,一级一级将state传下去以备组件使用。 - 高效的解决方案:React-Redux 提供
Provider组件,可以让容器组件拿到state。
上面代码中,import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp); render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。- 低效的解决方案:将
React- Router 路由库
使用React-Router的项目,与其他项目没有不同之处,也是使用Provider在Router外面包一层,毕竟Provider的唯一功能就是传入store对象。
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
);