创建counter项目
create-react-app counter
删除多余文件,src目录只保留app.js和index.js
index.js内容如下:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App/>,
document.getElementById('root')
);
app.js内容如下:
import React from 'react'
export default class App extends React.Component {
increase = () => {
console.log('+1')
}
decrease = () => {
console.log('-1')
}
render() {
return (
<div>
<p><span style={{fontSize: '50px'}}>0</span></p>
<p>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.increase}> +
</button>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.decrease}> -
</button>
</p>
</div>
);
}
}
安装redux
yarn add redux
安装后,启动:yarn start
,运行效果如图:
得到当前状态
目前页面上显示的数字是写死的,而实际应该从状态中获取,这样方便我们做加减法时修改状态,使页面上的数字得到自动更新。
redux的状态都是保存在store
对象之中的,所以我们要做的第一步是引入redux,并创建store对象。
在app.js
第二行添加如下代码:
import {createStore} from 'redux'
Redux 提供
createStore
这个函数,用来生成Store
。
- Store
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
接着在下一行创建store对象:
const store = createStore((state,action)=>{
console.log('state:', state)
console.log('action:', action)
return state
})
createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。接受的函数中有两个入参,state和action,state代表当前的状态,action为当前的动作
当前时刻的State
,可以通过store.getState()
拿到。
当刷新页面时,观察控制台,可以看到如下结果:
可以看到,页面在渲染后,打印了state
和action
两个参数,其中state
的值为undefined
,action
为一个对象,该对象有一个type
属性,值为@@redux/INITe.m.x.f.8.p
如果我们希望state的初始值是0,那么可以给state这个参数加上默认值。
const store = createStore((state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
return state
})
再次刷新页面,可以看到控制台结果如下:
- Action
Action
是一个对象,该对象包含多个属性,其中的type
属性是必须的,表示Action
的名称。其他属性可以自由设置,社区有一个规范可以参考。Action
描述当前发生的事情。改变State
的唯一办法,就是使用Action
。它会运送数据到Store
。
现在我们把页面中(app.js)写死的0替换为当前状态
将以下代码
<p><span style={{fontSize: '50px'}}>0</span></p>
替换为:
<p><span style={{fontSize: '50px'}}>{store.getState()}</span></p>
此时,页面上显示的数字0就是从状态中获取的了。
改变状态
现在我们希望点击加号后,改变状态值,使其结果变为1。
前面说了,改变 State
的唯一办法,就是使用 Action
。
在increase
函数中,创建一个action对象,内容如下:
const action = {
type: 'increment',
number: 1
}
这个action包含了两个属性,其中type
属性是必须的,内容为increment
,代表当前要执行的是加
这个动作。number
是我们自己取的属性名(可以改成其他的),值为1,代表执行加这个动作后,当前状态需要加上1。
前面提到所有的数据都保存在store对象,要完成加法,需要先从store中取得当前状态值,并加上action.number
的值,最后把结果存入当前状态。
与之前react通过this.setState来更新状态不同,store并没有setState这个方法。store提供了dispatch
方法来把action传递出去。
increase = () => {
const action = {
type: 'increment',
number: 1
}
store.dispatch(action)
}
现在点击页面上的加号按钮,观察控制台,可以看到如下结果:
可以看到,当点击了加号后,action对象被传递到了createStore
接收的函数参数这里。这个函数被称为Reducer
。
- Reducer
Store
收到Action
以后,必须给出一个新的State
,这样 View 才会发生变化。这种State
的计算过程就叫做Reducer
。Reducer
是一个函数,它接受Action
和当前State
作为参数,返回一个新的State
。
将以下代码:
const store = createStore((state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
return state
})
改写为:
const reducer = (state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
return state
}
const store = createStore(reducer)
然后根据action
的type
属性来决定是做加法还是减法,根据number
属性来决定加多少和减多少。
进一步完善代码为:
const reducer = (state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
switch (action.type) {
case 'increment':
return state + parseInt(action.number)
case 'decrement':
return state - parseInt(action.number)
default:
return state
}
}
刷新网页,多次点击加号,可以看到控制台有如下结果:
可以看到state已发生了变化。
监听状态变化
观察控制台可以看到每次点击加号后,state都发生了变化,但是页面上的数字却还是0,没有变化。这时需要调用store
的subscribe
方法来设置监听,一旦state发生变化,就自动执行这个函数。
在以下代码:
const store = createStore(reducer)
下方添加如下代码:
store.subscribe(() => {
console.log('监听状态:',store.getState())
})
然后刷新网页,点击加号,观察控制台:
可以看到每次点击后,state发生了变化的同时,redux都会自动调用store的subscribe方法。也就是说,我们只需要把ReactDOM.render
写在subscribe方法里就可以实现state更新后view的自动刷新了。
根据状态自动刷新
由于ReactDOM.render
是写在index.js
中的,所以store对象也应该写到index.js里。
改写后的index.js代码如下:
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import App from './App'
const reducer = (state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
switch (action.type) {
case 'increment':
return state + parseInt(action.number)
case 'decrement':
return state - parseInt(action.number)
default:
return state
}
}
const store = createStore(reducer)
ReactDOM.render(
<App store={store}/>,
document.getElementById('root')
)
store.subscribe(() => {
ReactDOM.render(
<App store={store}/>,
document.getElementById('root')
)
})
通过<App store={store}/>
像app.js传递store对象。
改写后的app.js内容如下:
import React from 'react'
export default class App extends React.Component {
increase = () => {
const {store} = this.props
const action = {
type: 'increment',
number: 1
}
store.dispatch(action)
}
decrease = () => {
const {store} = this.props
const action = {
type: 'decrement',
number: 1
}
store.dispatch(action)
}
render() {
const {store} = this.props
return (
<div>
<p><span style={{fontSize: '50px'}}>{store.getState()}</span></p>
<p>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.increase}> +
</button>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.decrease}> -
</button>
</p>
</div>
);
}
}
现在可以根据状态自动刷新网页了,效果如图:
使用react-redux
概念
react-redux
是一个react插件库,专门用来简化react应用中使用redux。
安装:yarn add react-redux
组件分类
React-Redux
将所有组件分成两大类
- 1)UI组件
a.只负责 UI 的呈现,不带有任何业务逻辑
b.通过props接收数据(一般数据和函数)
c.不使用任何 Redux 的 API
d.一般保存在components文件夹下 -
2)容器组件
a.负责管理数据和业务逻辑,不负责UI的呈现
b.使用 Redux 的 API
c.一般保存在containers文件夹下
在src目录下,新建components
文件夹和containers
文件夹,其中components
文件夹用于放UI组件,containers
文件夹用于放容器组件。
UI组件:Counter
在components
文件夹下新建Counter.jsx,内容如下:
import React from 'react'
import PropTypes from 'prop-types'
/**
* UI组件
*/
export default class Counter extends React.Component {
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired
}
increase = () => {
this.props.increment(1)
}
decrease = () => {
this.props.decrement(1)
}
render() {
const {count} = this.props
return (
<div>
<p><span style={{fontSize: '50px'}}>{count}</span></p>
<p>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.increase}> +
</button>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.decrease}> -
</button>
</p>
</div>
);
}
}
通过PropTypes
限定了该组件依赖于count
、increment
、decrement
三个属性,其中count
用于存放点击加减后的结果,是一个数字类型,increment
和decrement
则为函数类型,用于实现具体的加法和减法操作。这三个属性均为父组件传递过来的,该组件并不关心实现过程,只负责调用和显示。
容器组件:App
在containers
文件夹下新建App.jsx,该组件通过包装UI组件生成容器组件,react-redux提供了connect
高阶函数来实现,语法为:
connect(
mapStateToprops,
mapDispatchToProps
)(Counter)
connect函数接收两个参数,mapStateToprops
函数用于将特定state数据映射(转换)成标签的一般属性传递给UI组件(Counter),mapDispatchToProps
函数用于将包含dispatch函数调用语句的函数映射(转换)成标签的函数属性传递给UI组件(Counter)。
属性分为两大类:函数属性、一般属性(不是函数的都是一般属性)
函数属性为子向父通信
一般属性为父向子通信
App.jsx完整内容如下:
import {connect} from 'react-redux'
import Counter from '../components/counter'
/**
* 应用根组件:通过包装UI组件生成容器组件
*/
/**
* 将特定state数据映射(转换)成标签的一般属性传递给UI组件(Counter)
* redux在调用此函数时,传入了store.getState()的值
*/
const mapStateToprops = (state) => {
//返回的对象的所有属性传递给UI组件
return {
count: state
}
}
/**
* 将包含dispatch函数调用语句的函数映射(转换)成标签的函数属性传递给UI组件(Counter)
* redux在调用此函数时,传入了store.dispatch的值
*/
const mapDispatchToProps = (dispatch) => {
return {
increment: (number) => dispatch({
type:'increment',
number
}),
decrement: (number) => dispatch({
type:'decrement',
number
})
}
}
export default connect(
mapStateToprops, //用来指定传递哪些一般属性
mapDispatchToProps //用来指定传递哪些函数属性
)(Counter)
mapStateToprops和mapDispatchToProps的返回值都是一个对象,该对象的所有属性都会传递到UI组件,使UI组件可以通过this.props获取到。
入口文件index.js
修改src目录下的index.js,删除之前的subscribe监听变化的代码,同时引入react-redux的Provider组件,Provider组件会将接收到的store对象提供给所有的容器组件
完整index.js内容如下:
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore} from 'redux'
import {Provider} from 'react-redux'
import App from './containers/App'
const reducer = (state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
switch (action.type) {
case 'increment':
return state + parseInt(action.number)
case 'decrement':
return state - parseInt(action.number)
default:
return state
}
}
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
App.jsx的简写
import {connect} from 'react-redux'
import Counter from '../components/counter'
const increment = (number) => ({
type: 'increment',
number
})
const decrement = (number) => ({
type: 'decrement',
number
})
export default connect(
state => ({count: state}),
{increment, decrement}
)(Counter)
redux 异步编程
安装redux插件(异步中间件)
yarn add redux-thunk
修改index.js
index.js做如下更改:
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))
完整index.js代码如下:
import React from 'react'
import ReactDOM from 'react-dom'
import {createStore, applyMiddleware} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import App from './containers/App'
const reducer = (state = 0, action) => {
console.log('state:', state)
console.log('action:', action)
switch (action.type) {
case 'increment':
return state + parseInt(action.number)
case 'decrement':
return state - parseInt(action.number)
default:
return state
}
}
const store = createStore(reducer, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
修改Counter.jsx
import React from 'react'
import PropTypes from 'prop-types'
/**
* UI组件
*/
export default class Counter extends React.Component {
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired,
increaseAsync: PropTypes.func.isRequired,
}
increase = () => {
this.props.increment(1)
}
decrease = () => {
this.props.decrement(1)
}
increaseAsync = () => {
this.props.increaseAsync(1)
}
render() {
const {count} = this.props
return (
<div>
<p><span style={{fontSize: '50px'}}>{count}</span></p>
<p>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.increase}> +
</button>
<button style={{fontSize: '30px', 'width': '50px', 'height': '50px'}} onClick={this.decrease}> -
</button>
<button style={{fontSize: '30px', 'width': '170px', 'height': '50px'}} onClick={this.increaseAsync}> 1秒后增加
</button>
</p>
</div>
);
}
}
修改app.jsx
import {connect} from 'react-redux'
import Counter from '../components/counter'
/**
* 应用根组件:通过包装UI组件生成容器组件
*/
const increment = (number) => ({
type: 'increment',
number
})
const decrement = (number) => ({
type: 'decrement',
number
})
const increaseAsync = (number) => {
return dispath => {
//1.执行异步代码
setTimeout(() => {
//2.分发action
dispath(increment(number))
}, 1000)
}
}
export default connect(
state => ({count: state}),
{increment, decrement, increaseAsync}
)(Counter)
redux调试工具
1.谷歌浏览器安装Redux DevTools
插件
2.安装工具依赖包
yarn add redux-devtools-extension --dev
3.编码
import {composeWithDevTools} from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools(applyMiddleware(thunk)))
4.安装后演示截图