搞懂redux
redux
redux原理
Redux 是一种新型的前端“架构模式”(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。
Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。
-
React和Redux
事实上是两个独立的产品,项目中可以使用 React 而不使用Redux ,也可以使用 Redux 而不使用 React
-
什么是React-redux
就是把 Redux 这种架构模式和 React 结合起来的一个库,是 Redux 架构在 React 中的体现,利用这个库能够大大简化代码的书写
Redux设计和使用的三项基本原则
-
store是必须是唯一的
-
只有store能改变自己的内容
reducer 可以接受state,但是绝对不能修改state
-
Reducer必须是一个纯函数
纯函数指的是,给固定的输入,就一定会有固定的输出,而且不会有任何副作用
什么时候需要Redux
Redux作者:如果你不知道是否需要 Redux,那就是不需要它
只有遇到 React 实在解决不了的问题,你才需要 Redux
- 某个组件的状态,需要共享
比如:用户头像信息
- 某个状态需要在任何地方都可以拿到
- 在一个组件中需要改变全局状态
比如:底部tabbal的显示和隐藏
- 在一个组件中需要改变另一个组件的状态
redux和vuex的区别
redux与使用的框架无关系;如果那天不用了,代码注释或删除没问题的,组件不影响,store代码与组件时分离的;
vuex是用this.$store.state=xxx,通过组件实例获取,是耦合的关联的;
redux使用
redux使用步骤
引入redux
import {createStore} from 'redux';
创建一个仓库
const store = createStore(Reducer);
使用
- 获取state
store.getState()
- 修改state:唯一修改state的方式就是reducer
store.dispath(action)
- 监听state
store.subscribe(fn)
- 获取state
redux核心
-
reducer
- Reducer 必须是一个纯函数,
- 用于指定state修改逻辑,
- 它接受当前 state 和 action 作为参数,并根据state和action的值返回新的state
-
store
- 常用方法
store.getState()
获取仓库最新state状态 对比:Vue: this.$store.state
store.dispatch()
触发reducer,并修改statestore.subscribe()
监听state修改 ,类似于观察者 可以监听多个
- 常用方法
-
action
格式:
{ type:'xxx' }
-
state: 状态,每一个存放数据的地方,为什么叫状态?因为可以随便修改
- state有个方法:store.getState() 获取最新状态,只要state有修改,getState就是获取最新的状态;
/**
* redux核心概念
* store
* reducer state的修改逻辑
* 参数
* state
* action
* action
* 格式:{type:''}
*/
// 1. 引入reduxt
import {
createStore
} from 'redux'
// 这个应用的初始状态,可以作为state的默认值
const initState = {
currentUser: {},
// 默认显示菜单
showMenu: true,
}
// reducer 是一个函数,它接受 当前state和 收到的action作为参数,返回一个新的state
// dispatch({type:'currentUser',user})
// dispatch({type:'showMenu'})
const reducer = function (state = initState, action) {
// console.log('reducer', state, action)
switch (action.type) {
case 'login':
return {
...state,
currentUser: action.user
}
case 'logout':
return {
...state,
currentUser:{}
}
case 'showmenu':
return {
...state,
showMenu: action.showMenu
}
default:
return state
}
}
// 2. 创建仓库
//实际应用中,Reducer 函数不用手动调用,store.dispatch方法会触发 Reducer 的自动执行。
//为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
const store = createStore(reducer)
// 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store
// 以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
export default store
// 3. 使用
// 获取:store.getState()
// 修改:store.dispatch(action) // action —— {type:'xxx'[,传递的参数]}
// 监听:store.subscribe(fn) // fn 是一个函数
// 可以监听多个
// store.subscribe(fn1) // fn1 是一个函数
// store.subscribe(fn2) // fn2 是一个函数
login组件:
import React from 'react'
import CryptoJS from 'crypto-js'
import request from '@/utils/request'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import userAction, { login } from '../store/actions/user.js'
// 引入 store 前提必须导出 store
import store from '../store'
// 监听state修改
store.subscribe(function(){
// 这个函数在state有修改时执行(调用dispatch时执行)
const state = store.getState()
console.log('state=', state)
})
import { Form, Input, Button, Checkbox } from 'antd';
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 4, span: 16 },
};
// 类组件
class Login extends React.Component {
// 成功时
onFinish = async ({ username, password, mdl }) => {
password = CryptoJS.SHA256(password)
password = CryptoJS.enc.Hex.stringify(password)
const data = await request.get('user/login', {
username,
password,
mdl
})
// console.log('Success:', data);
if (data.status === 200) {
this.props.history.push('/profile')
// localStorage.setItem('currentUser', JSON.stringify(data.data))
console.log("login.props=", this.props)
// 触发reducer, 并修改state 把user:data.data 传到action中,后面在store的index文件中 , 传到reducer 是 action.user
store.dispatch({type:'login},user:data.data)
// 当使用高阶组件 mapDispatchToProps 写下面这种即可,就不用上面写法
// this.props.login(data.data)
}
};
// 失败时
onFinishFailed = errorInfo => {
console.log('Failed:', errorInfo);
};
// 校验
rules = {
username: [{ required: true, message: '用户名必填' }],
password: [{ required: true, message: '密码必填' }],
}
render() {
return <div>
<div style={{ margin: 10, fontSize: 20 }}>用户登录</div>
<Form
{...layout}
name="basic"
initialValues={{ remember: true }}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
>
<Form.Item
label="用户名"
name="username"
rules={this.rules.username}
>
<Input />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={this.rules.password}
>
<Input.Password />
</Form.Item>
<Form.Item {...tailLayout} name="mdl" valuePropName="checked">
<Checkbox>下次免登陆</Checkbox>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
}
}
// 下面是使用高阶组价后写法
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })
// 你要把什么映射到 props 就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
return {
// login方法 调用login方法传入user
// 当return 中写入login方法,则props中存在login
login(user) {
dispatch({ type: 'login', user })
},
// 但是如果还想要原来的dispatch ,原样传过去就可以了
dispatch
}
// return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)
export default Login
redux工作流程
理解:
假如在组件中有个登录按钮,登录成功后,想要修改redux上面的数据,通过dispatch来修改,使用dispatch发起一个action,那么从component 到action 就连上一条线;
然后store拿到这个action ,而reducer是创建时传入的值,所以当调用dispatch时,进入store,而store内部会自动调用reducer ,注意 reducer是一个函数,store去帮我们触发它;
reducer里面的参数,一个state,一个action ,其中state是从调用时传递过来的,action也是调用时传递过来的;
1、用户发出 actions
;
比如用户的登录登出
store.dispatch(ation)
2、store 自动调用reducer,并且传入两个参数:当前的state
和 收到的action
,reducer会返回一个新的state
;
let nextState = todoApp(previousState, action)
3、state 一旦会变化,store 就会调用监听函数;
// 设置监听函数
store.subscribe(listener)
4、listener 可以通过 store.getState()
得到当前状态。如果使用的是react,这时可以触发渲染view;
function listener(){
let newState = store.getState()
component.setState(newState)
}
redux应用
1、用户的使用方式复杂
2、不同身份的用户有不同的使用方式(比如普通用户和管理员)
3、多个用户之间可以协作
4、与服务器大量交互,或者使用了WebSocket
5、View要从多个来源获取数据
以上根据阮一峰大佬的网站整理的笔记和引用,仅用于个人学习提升。
原网站地址:Redux 入门教程
手动实现简易版redux
/***
* 手动实现简易版redux
*
*/
export const createStore = function(reducer){
// 注意这是我自己封装的,确定需要传入一个reducer函数
let state = reducer()
// 数组用来存放监听的函数
let listeners = []
// 实现getState
const getState = function(){
return state
}
// 实现dispatch 注意:dispatch 会自动调用reducer
const dispatch =function(action){
state = reducer(state,action)
// 发布 当state改变时,触发subscribe 有修改就有监听
listeners.forEach(listener=>listener())
}
// 实现 subscribe 需要传入一个函数
const subscribe=function(fn){
// 订阅
listeners.push(fn)
}
// 实现 replaceReducer
const replaceReducer = function(newReducer){
reducer = newReducer
}
return {
getState,
dispatch,
subscribe,
replaceReducer
}
}
但是用组件间的state更新原理来实现,因为给组件添加state,修改state,而且还得监听它的状态,发现 比较繁琐,而且组件的刷新除了state更新,还有props 修改来刷新组件,这里不考虑父组件的刷新,所以推荐props的方式来实现。
props修改是通过父组件来传递的,但是父组件和我的组件时分开的,想要通过redux数据从props里面传到需要的组件里面,就可以实现这种功能,之前是通过高阶组价代理props,然后传递过来;自己写也可以,但是有人已经帮我们封装写好了;就是下面的react-redux;
react-redux桥接工具
为了方便使用,Redux 的作者封装了一个 React 专用的库 React-Redux,它提供了一些接口,用于Redux的状态和React的组件展示结合起来,以用于实现状态与视图的一一对应;
连接react组件与redux状态
原理:利用context + 高阶组件实现
-
通过 Provider 组件共享 store 状态
-
利用 connect() 高阶组件接收 store 数据
通过props把需要的数据传入目标组件
使用如下
安装 npm i react-redux -D
// 内部已经封装好 内部原理
const MyContext = React.createContext()
<MyContext.Provider />
// 直接用
<Provider store={store}></Provider>
// App.js
// mapStateToProps: 映射redux上的state到组件的props
App = connect(mapStateToProps, mapDispatchToProps)(App);
使用 react-redux 步骤
1、引入 import { Provider } from 'react-redux'
2、对根组件进行修改 通过 Provider 共享store数据
import React from 'react'
import ReactDOM from 'react-dom'
// 用于 redux
import { Provider } from 'react-redux'
import store from './store'
// 安装路由 react-router 和 react-router-dom
import {
HashRouter,
BrowserRouter,
Route
} from 'react-router-dom'
import App from './App'
// 编译时修改路由:hash路由、history路由
const Router = process.env.NODE_ENV === 'product' ? BrowserRouter : HashRouter
// 下面加了Provider之后,里面的store 底下所有子组件都可以共用store的数据
ReactDOM.render(
<Provider store={store}>
<Router>
<App />
{/* <Route component={App} /> */}
{/* 这里不加path 因为所有路径都要渲染这个App组件 */}
</Router>
</Provider>,
document.querySelector('#app')
)
3、App.js 组件 中使用 connect 高阶组件接收 store 数据
import React from 'react'
// 安装react-router 和 react-router-dom
import { Route, Redirect, Switch, NavLink, withRouter } from 'react-router-dom'
// 引入react-redux 中的 connect
import { connect } from 'react-redux'
import store from './store/index.js'
import { logout } from './store/actions/user'
// 导入路由 页面组件
import Home from './pages/Home.jsx'
// import Good from './pages/Good.jsx'
import Search from './pages/Search.jsx'
import Profile from './pages/Profile.jsx'
import Category from './pages/Category.jsx'
import Login from './pages/Login.jsx'
import Reg from './pages/Reg.jsx'
import IQ from 'pages/IQ.jsx'
// 导入 antd 样式
import { Button, Menu, Row, Col } from 'antd'
import { HomeOutlined, ContactsOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons'
// 导入样式
import './App.css'
// 高阶组件的使用 和下面ES7 装饰器一起使用
const mapStateToProps = function (state) {
return {
currentUser: state.user
}
}
const mapDispatchToProps = function (dispatch) {
return {
logout() {
// 注意这里的logout() 是生成的action
dispatch(logout())
}
}
}
// ES7 装饰器写法
@connect(mapStateToProps, mapDispatchToProps)
@withRouter
// 有状态改变用class组件
class App extends React.PureComponent {
state = {
menu: [{
text: '首页',
name: 'home',
path: '/home',
icon: <HomeOutlined />
},
{
text: '搜索',
name: 'search',
path: '/search',
icon: <TeamOutlined />
}, {
text: '分类',
name: 'category',
path: '/category',
icon: <ContactsOutlined />
}, {
text: '我的',
name: 'profile',
path: '/profile',
icon: <UserOutlined />
}],
current: '/'
}
goto = ({ key }) => {
// console.log(this.props) // 这里的打印的是跳转路由后, 渲染的组件的 props
// 跳转路由 有浏览器记录
// this.props.history.push(path)
// 跳转路由 无浏览器记录
// this.props.history.replace(path)
this.setState({
current: key
})
this.props.history.push(key)
}
gotoLR = (path) => {
this.props.history.push(path)
}
componentWillMount() {
// console.log("location=", this.props.location)
// console.log("props", this.props)
const { pathname } = this.props.location
if (pathname === "/") {
this.setState({
current: '/home'
})
} else {
this.setState({
current: pathname
})
// console.log("pathname=", pathname)
}
}
// componentWillUnmount(){
// console.log("unmont=",this.props)
// const { pathname } = this.props.location
// this.setState({
// current: pathname
// })
// }
render() {
const { menu, current } = this.state
// console.log('app.props=', this.props)
const { currentUser,logout } = this.props
// console.log('app.props=',this.props) // 这里的打印的是当前渲染的组件的 props
// 这时当main.js中为 <App /> 时能打印出history对象,因为已经使用高阶组件进行了包装
return (
<div>
<Row>
<Col span={12}>
<Menu onClick={this.goto} defaultSelectedKeys={[current]} selectedKeys={[current]} mode="horizontal">
{
menu.map(item => <Menu.Item key={item.path} >
{item.icon}
{item.text}
</Menu.Item>)
}
</Menu>
</Col>
<Col span={12} style={{ textAlign: 'right' }}>
{
currentUser._id ? <Button type="link" onClick={logout}>退出</Button> :
<>
<Button type="link" onClick={this.gotoLR.bind(this, '/login')}>登录</Button>
<Button type="link" onClick={this.gotoLR.bind(this, '/reg')}>注册</Button>
</>
}
</Col>
</Row>
<Switch>
<Route path="/home" component={Home}></Route>
{/* <Route path="/good" component={Good}></Route> */}
<Route path="/search" component={Search}></Route>
<Route path="/profile" component={Profile}></Route>
<Route path="/category" component={Category}></Route>
<Route path="/iq/:id" component={IQ} />
<Route path="/login" component={Login}></Route>
<Route path="/reg" component={Reg}></Route>
<Route path="/notfound" render={() => <div>404</div>}></Route>
{/* 重定向可以不加to */}
<Redirect from="/" to="/home" exact></Redirect>
{/* 404 */}
<Redirect to="/notfound"></Redirect>
</Switch>
{/* <nav>
<ul>
{
// 声明式导航
// menu.map(item=><li key={item.name}>
// <NavLink to={item.path} activeClassName="active" activeStyle={{fontWeight:"bold"}}>{item.text}</NavLink>
// </li>)
// 编程式导航
menu.map(item => <li key={item.name} onClick={this.goto.bind(this, item.path)}>
{item.text}
</li>)
}
</ul>
</nav> */}
</div>
)
}
}
// // 高阶组件(包装函数) 下面是不使用 ES7装饰器 的写法
// App = withRouter(App)
// // 你要把什么映射到 props 就 return 什么就可以
// // return 的可以进入 App的props 里面
// // mapStateToProps 有一个参数 里面是state 是 redux 的状态
// const mapStateToProps = function (state) {
// // state:redux 的状态
// return {
// currentUser: state.currentUser
// }
// }
// // connect里面有两个参数 当然这个参数可以是 a ,不一定是mapStateToProps
// App = connect(mapStateToProps)(App)
// // 这里的参数 mapStateToProps 是一个函数
export default App
4、login.js 组件中使用 connect 高阶组件
import React from 'react'
import CryptoJS from 'crypto-js'
import request from '@/utils/request'
// 引入高阶组件 connect
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import userAction, { login } from '../store/actions/user.js'
import { Form, Input, Button, Checkbox } from 'antd';
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 4, span: 16 },
};
// 类组件
class Login extends React.Component {
// 成功时
onFinish = async ({ username, password, mdl }) => {
password = CryptoJS.SHA256(password)
password = CryptoJS.enc.Hex.stringify(password)
const data = await request.get('user/login', {
username,
password,
mdl
})
// console.log('Success:', data);
if (data.status === 200) {
this.props.history.push('/profile')
// localStorage.setItem('currentUser', JSON.stringify(data.data))
console.log("login.props=", this.props)
// 这里 就不用写 props.dispatch({type:'login},user:data.data)
// 当使用高阶组件 mapDispatchToProps 写下面这种即可
this.props.login(data.data)
}
};
// 失败时
onFinishFailed = errorInfo => {
console.log('Failed:', errorInfo);
};
// 校验
rules = {
username: [{ required: true, message: '用户名必填' }],
password: [{ required: true, message: '密码必填' }],
}
render() {
return <div>
<div style={{ margin: 10, fontSize: 20 }}>用户登录</div>
<Form
{...layout}
name="basic"
initialValues={{ remember: true }}
onFinish={this.onFinish}
onFinishFailed={this.onFinishFailed}
>
<Form.Item
label="用户名"
name="username"
rules={this.rules.username}
>
<Input />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={this.rules.password}
>
<Input.Password />
</Form.Item>
<Form.Item {...tailLayout} name="mdl" valuePropName="checked">
<Checkbox>下次免登陆</Checkbox>
</Form.Item>
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
登录
</Button>
</Form.Item>
</Form>
</div>
}
}
// 高阶组件
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面 注意默认的是存在dispatch
const mapStateToProps = ({ currentUser }) => ({ currentUser })
// 你要把什么映射到 props 就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是 dispatch
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
return {
// login方法 调用login方法传入user
// 当return 中写入login方法,则props中存在login
login(user) {
dispatch({ type: 'login', user })
},
// 但是如果还想要原来的dispatch ,原样传过去就可了
dispatch
}
// return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)
export default Login
在 login 组件触发了dispatch 就会进入到redux的reducer里面,把state进行修改,而state通过props传入到了App组件,这时props在App中有修改(通过高阶组件修改的)而props有修改,组件就会刷新;然后登陆注册按钮 就会改为 退出按钮,发现监听都不用写;
redux模块化
模块化为了方便管理,分工明确
redux模块化
也可以成为 reducer模块化
目录如下:
1、store 文件夹下新建一个reducers 文件夹,里面新建文件如目录 ;
2、common.js 全局配置、user.js 用户配置、cart.js 购物车配置等等;
3、把多个reducer合并为一个reducer,通过redux的一个方法:combineReducers()
4、在reducers 的下面的 index.js中合并 reducer
user.js
// 刷新时,先从localstorage 中获取用户信息
let currentUser = localStorage.getItem('currentUser') //null
try {
currentUser = JSON.parse(currentUser) || {}
} catch (err) {
currentUser = {}
}
const initState = {
...currentUser
}
function reducer(state=initState,action){
// console.log("action",action)
switch(action.type){
// 登录
case 'login':
localStorage.setItem('currentUser',JSON.stringify(action.user))
return action.user
// 登出
case 'logout':
localStorage.removeItem('currentUser')
return {}
// 更新用户
case 'update_user':
// console.log(1)
return {
...state,
...action.user
}
default:
return state
}
}
export default reducer
cart.js
const initState = {
goodlist:[],
totalPrice:0
}
function reducer(state=initState,action){
switch(action.type){
// 添加商品到购物车
// {type:'add_to_cart',goods}
case 'add_to_cart' :
return {
...state,
goodlist:[action.goods,...state.goodlist]
}
// 从购物车移除商品
// {type:'remove_from_cart',id}
case 'remove_from_cart' :
return {
...state,
goodlist:state.goodlist.filter(item=>item.id!==action.id)
}
// 清空购物车所有商品
// {type:'clear_cart'}
case 'remove_from_cart' :
return {
...state,
goodlist:[]
}
default:
return state
}
}
export default reducer
common.js
const initState = {
showMenu:true
}
function reducer(state=initState,action){
switch(action.type){
// 菜单栏显示
case 'show_menu' :
return {
...state,
showMenu:action.show
}
default:
return state
}
}
export default reducer
reducers下面的 index.js
import {combineReducers} from 'redux'
import cartReducer from './cart'
import userReducer from './user'
import commonReducer from './common'
// 把多个reducer合并成一个reducer
const reducer = combineReducers({
cart:cartReducer,
user:userReducer,
common:commonReducer,
})
export default reducer
store下面的index.js
/**
* redux核心概念
* store
* reducer state的修改逻辑
* 参数
* state
* action
* action
* 格式:{type:''}
*/
// 引入reducers 文件下面的 index.js
import reducer from './reducers'
// 模块化 : 把这部分的reducer提取出去
// // 这个应用的初始状态,可以作为state的默认值
// const initState = {
// currentUser: {},
// // 默认显示菜单
// showMenu: true,
// }
// // reducer必须为纯函数
// // dispatch({type:'changeUser',user})
// // dispatch({type:'changeMenu'})
// // reducer 是一个函数,它接受 当前state和 收到的action作为参数,返回一个新的state
// const reducer = function (state = initState, action) {
// // console.log('reducer', state, action)
// switch (action.type) {
// case 'login':
// return {
// ...state,
// currentUser: action.user
// }
// case 'logout':
// return {
// ...state,
// currentUser:{}
// }
// case 'showmenu':
// return {
// ...state,
// showMenu: action.showMenu
// }
// default:
// return state
// }
// }
//实际应用中,Reducer 函数不用手动调用,store.dispatch方法会触发 Reducer 的自动执行。
//为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入createStore方法。
const store = createStore(reducer)
// 上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。
// 以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
export default store
// 3. 使用
// 获取:store.getState()
// 修改:store.dispatch(action)
// 监听:store.subscribe(fn)
Action Creator 用于简化代码操作
Action Creator
每次编写action对象比较麻烦,可以封装一个函数用于生成action
action creator 一个用于创建action的函数
export function updateCart(todo){
return {
type:'UPDATE_CART',
payload:todo
}
}
目录中的store下的actions文件夹的user.js
// 用户的登录和登出的dispatch操作 比如 dispatch({type:'xxxx'})
export function login(user) {
return {
type: 'login',
user
}
}
export function logout() {
return {
type: 'logout'
}
}
export default {
login,
logout
}
// 假如用这个export default形式的导出 如果在login组件中使用,可以这样写:
// import userAction from '../store/actions/user.js'
// 或者
// import { login } from '../store/actions/user.js'
login.js组件中使用 action Creator
import userAction, { login } from '../store/actions/user.js'
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })
// 你要把什么映射到 props 就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
return {
// login方法 调用login方法传入user
// 当return 中写入login方法,则props中存在login
login(user) {
dispatch(login(user))
},
// 但是如果还想要原来的dispatch ,原样传过去就可了
dispatch
}
// return bindActionCreators(userAction,dispatch)
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)
bindActionCreators
利用redux的
bindActionCreators
把ActionCreator
中默认导出的所有方法 ( 注意是export default
中的方法) 绑定到组件props并自动隐式调用dispatch(action)
import {bindActionCreators} from redux;
import {connect} from 'react-redux';
import ActionCreator from 'actions';
//...
MyComponent = connect(state=>state,dispatch=>bindActionCreators(ActionCreator,dispatch))(MyComponent)
login.js组件中使用 bindActionCreators
import { bindActionCreators } from 'redux'
// 注意:使用bindActionCreators 要使用默认导出的 export default 下面的 userAction 就是默认导出的
import userAction, { login } from '../store/actions/user.js'
// 注意箭头函数后面不能直接跟着{} 因为会它会把{} 解析为代码块
// const mapStateToProps = (state) => ({currentUser.state}) //等效于下面
const mapStateToProps = ({ currentUser }) => ({ currentUser })
// 你要把什么映射到 props 就 return 什么就可以
// return 的可以进入 Login的props 里面
// mapDispatchToProps 有一个参数 里面是dispatch
// 注意:当写了mapDispatchToProps,默认的dispatch 就没有了
const mapDispatchToProps = function (dispatch) {
// return {
// // login方法 调用login方法传入user
// // 当return 中写入login方法,则props中存在login
// login(user) {
// dispatch({ type: 'login', user })
// },
// // 但是如果还想要原来的dispatch ,原样传过去就可了
// dispatch
// }
// 直接用下面的写法就可以,不用上面 return 的写法 dispatch不用写,里面login() 也不用写了
return bindActionCreators(userAction,dispatch)
// {login,logout} 等效于下面的代码 内部帮我们实现了以下代码
// {
// login : function(user){
// dispatch(login(user))
// },
// logout : function(){
// dispatch(logout())
// }
// }
}
// connect 的两个参数的第二个参数 mapDispatchToProps 把dispatch映射到props里面
Login = connect(mapStateToProps, mapDispatchToProps)(Login)
redux刷新浏览器,数据就消失,为了持久化需要配合本地存储
比如登录时,把用户信息存在本地存储里面
// 刷新时,先从localstorage 中获取用户信息
let currentUser = localStorage.getItem('currentUser') //null
try {
currentUser = JSON.parse(currentUser) || {}
} catch (err) {
currentUser = {}
}
const initState = {
...currentUser
}
function reducer(state=initState,action){
// console.log("action",action)
switch(action.type){
// 登录
case 'login':
localStorage.setItem('currentUser',JSON.stringify(action.user))
return action.user
// 登出
case 'logout':
localStorage.removeItem('currentUser')
return {}
// 更新用户
case 'update_user':
// console.log(1)
return {
...state,
...action.user
}
default:
return state
}
}
export default reducer
收藏功能的实现
import React from 'react'
import request from '@/utils/request'
import { connect } from 'react-redux'
import { Avatar, Button, Rate, message } from 'antd'
// 小图标
import { UserOutlined, HeartOutlined } from '@ant-design/icons'
// 富文本编辑器
import E from 'wangeditor'
@connect(state => ({
currentUser: state.user
}), (dispatch) => ({
updateUser(user) {
dispatch({ type: 'update_user', user })
}
}))
class IQ extends React.PureComponent {
state = {
data: {},
// editorContent: ''
}
follow = async () => {
const { data } = this.state
// console.log("data_id",data._id)
const { currentUser, updateUser } = this.props
// console.log("updateUser",updateUser)
// console.log("issc",currentUser.focus.some(item => item === data._id)) //能打印false或true
if (Object.keys(currentUser).length === 0) {
message.error("请登录后收藏")
return false
}
// 判断是否收藏
const issc = currentUser.focus.some(item => item === data._id)
const result = await request.put(`iq/${data._id}/follow`, {
userid: currentUser._id,
action: issc ? 'del' : 'add'
})
// console.log("result",result)
if (result.status === 200) {
if (issc) {
// 取消收藏
// this.setState({
// data: {
// ...data,
// focus: data.focus.filter(item => item !== data._id)
// }
// })
updateUser({
focus: currentUser.focus.filter(id => id !== data._id)
})
} else {
//添加收藏
// this.setState({
// data: {
// ...data,
// focus: [data._id,...data.focus]
// }
// })
// console.log("data_id", data._id, currentUser.focus)
updateUser({
focus: [data._id, ...currentUser.focus]
})
// console.log("data_id", data._id, currentUser.focus)
}
}
}
async componentWillMount() {
// console.log("props=", this.props)
// console.log("componentWillMount",this.state)
const { id } = this.props.match.params
const data = await request.get('/iq/' + id)
// console.log("data=", data)
this.setState({
data: data.data
})
}
async componentDidMount() {
// console.log(this.props)
const { id } = this.props.match.params
const data = await request.get('/iq/' + id)
this.setState({
data: data.data
})
// 更新用户浏览记录
const { status } = await request.get(`/iq/${id}/hot`)
// console.log("status",status)
if (status === 200) {
this.setState({
data: {
...this.state.data,
hot: this.state.data.hot + 1
}
})
}
render() {
// console.log("props=",this.props)
const { data } = this.state
const { currentUser } = this.props
// console.log("user", data)
// console.log("iq",currentUser)
// console.log("收藏",currentUser.focus.some(item=>item === data._id) ? '取消' : '收藏')
return (
<div>
<div style={{ marginLeft: 20 }}>
<h1>{data.question}</h1>
<div>
{
data.user
? <span><Avatar size="small" icon={<UserOutlined />} />{data.user.username}</span>
: ''
}
<span style={{ margin: 20 }}>{data.hot}浏览</span>
<span>评分<Rate disabled value={data.difficulty} /></span>
{
// Object.keys(currentUser).length !== 0
data.user !== null
?
<Button onClick={this.follow} style={{ margin: 20 }} type="link" icon={<HeartOutlined />}>
{currentUser.focus.some(item => item === data._id) ? '取消' : '收藏'}
</Button>
:
<Button onClick={this.follow} style={{ margin: 20 }} type="link" icon={<HeartOutlined />}>收藏</Button>
}
</div>
</div>
)
}
}
export default IQ;
redux 中间件 (只要有异步就在中间件中处理)
有没有发现我们上面的数据处理都是同步的,都是数据回来之后,直接修改state,但是在项目中可能存在大量的异步操作,可能有人会说在reducer里面进行操作也可以,但是不建议这样做,因为reducer就是一个纯函数,不应该搞的复杂;
那么下面引入需要用到的 中间件middlewore
在vue中使用axios可以解决异步的问题,比如在vuex中异步处理,但是在react中没有这种处理的方式所以不可能处理异步,只能用中间件来处理;
什么是中间件?在操作到达目的之前进行的拦截操作,就是中间处理;
所有的异步操作都会放到中间件处理,比如ajax请求,请求的数据完成后,在中间件里面发起dispatch ,然后进入reducer中,再修改state
那怎么从第一图变为第二个图呢?
在store文件下的index.js中
// 2. 创建仓库 也就是说如果往第三个参数传 中间件 那么代码会从第一个图变为第二图了
const store = createStore(reducer,initState, middleware)
redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作
中间件的由来与原理、机制
对 dispatch 改装,实现 redux 异步处理中间件
常用中间件
-
redux-chunk
-
redux-promise
-
redux-saga(推荐使用)
- Generator 生成器函数
-
返回一个迭代器 Iterator
-
yield 暂停 (后面可以写ajax请求,类似于await效果)
-
return 结束
-
Iterator 迭代器(遍历)
-
next() 返回格式为:{value,done}的对象
-
value: 迭代到当前位置的值
-
done: 迭代器是否迭代完毕
-
-
for…of 能遍历具有迭代器特性的数据
-
内部会自动调用迭代器的next方法
- Effect
- call
- apply
- put
- takeEvery
- takeLatest
- Generator 生成器函数
使用中间件
- 利用
applyMiddleware
接受中间件(可同时接受多个中间件) - 通过
createStore
的第3个参数连接中间件与store
import {createStore,applyMiddleware} from 'redux';
// 引入 saga
import createSagaMiddleware from 'redux-saga';
// 1.引入自定义saga配置文件
import rootSaga from './rootSaga.js';
// 2.创建saga中间件
const sagaMiddleware = createSagaMiddleware();
// 3.将 sagaMiddleware 连接至 Store
let enhancer = applyMiddleware(sagaMiddleware)
const store = createStore(reducer,enhancer);
// 4.运行 Saga配置
sagaMiddleware.run(rootSaga);
多个 enhancer 使用 redux.compose 组合成单个 enhancer
import {createStore,applyMiddleware,compose} from 'redux'
let enhancer = compose(...enhancer);
// 注意 这里的 enhancer 就是第三个参数,它内部会判断传入的参数 enhancer是 函数类型
// 而第二个参数是state,是对象类型 {},
const store = createStore(reducer,[state,] enhancer);
或者:
/**
* redux核心概念
* store
* reducer state的修改逻辑
* 参数
* state
* action
* action
* 格式:{type:''}
*/
// 1. 引入redux
import {createStore, applyMiddleware} from 'redux'
import reducer from './reducers'
import {composeWithDevTools} from 'redux-devtools-extension'
// 引入saga
import mysaga from './saga'
// saga 使用步骤1:1、引入saga
import createSagaMiddleware from 'redux-saga'
// saga 使用步骤2:创建saga中间件 注意不能直接写入createStore中,需要用到redux中的applyMiddleware
const sagaMiddleware = createSagaMiddleware()
// 2. 创建仓库
// saga 使用步骤3:把saga 中间件与store进行结合
let enhancer = applyMiddleware(sagaMiddleware)
enhancer = composeWithDevTools(enhancer)
// 注意 这里的 enhancer 是第三个参数,它内部会判断传入的参数 enhancer是 函数类型
// 而第二个参数是state,是对象类型 {},const store = createStore(reducer,[state,] enhancer)
const store = createStore(reducer, enhancer)
// saga 使用步骤4:运行saga配置 下一步就是怎么写saga配置
sagaMiddleware.run(mysaga)
export default store
新建一个saga文件夹下面的index.js 用于saga配置
/***
* saga 配置
* 使用 es6新特性 : Generator
*
*/
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import request from '@/utils/request'
function* getUser(action){
console.log('getUser=',action) // {type:'update_user_async'}
//注意这个action是传入过来的参数 通过this.props.dispatch({type:'get_user',userid:xxxxxx})
// 这样就可以通过action拿到用户的id
const {data} = yield request.get('/user/'+action.userid)
console.log('user=',data)
/*
拿到用户的数据 然后修改state 可以 store.dispatch({type: 'update_user',user:data})
但是 dispatch 从哪里来呢? 可以 import store from '@/store'
*/
// 其实不用上面的方式也可以解决,saga 里面有个put,而put 就是dispatch
// yield put({type:'update_user',user:data})
// 改为下面写法更好
yield put({type:action.type.replace('_async',''),user:data})
}
function* getNewIQ(){
}
function* init(){
console.log('init')
// 监听用户 dispatch 操作 这里加_async 代表是异步的意思 那前面发送的也应该是update_user_async
// 比如在 profile的 钩子函数 componentDidMount中,this.props.dispatch({type:'get_user_async',userid:'xxxx'})
// 建议上面和下面的action写的一样,
yield takeLatest('update_user_async',getUser)
yield takeLatest('get_newid_async',getNewIQ)
/*
takeEvery, takeLatest 本质一样,但是 takeLatest 只保留最后一次的触发,
比如前面的dispatch,是点击按钮才触发,但是不小心点击多次,会连续触发多次,而用takeEvery会触发多次
而用 takeLatest 只会保留最后一次,算是像 经常见到的 节流和防抖 操作,建议用 takeLatest
*/
}
export default init
-
sagaAction
sagaAction = reducerAction + ‘_async’
-
reducerAction
补充一点:就是单元测试,把上面代码改进一下
```javascript
/***
* saga 配置
* 使用 es6新特性 : Generator
*
*/
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import request from '@/utils/request'
// 单元测试 利用 apply 或者 call
function* getUser(action){
// call 是把要执行的作为参数传入,内部会把调用request方法,
// 但是 request需要参数,继续往里面传入参数即可,效果时一样的,
// 最终 调用request 并把参数传入request 里面
// 这个call就为了单元测试
const {data} = yield call(request.get,'/user/'+action.userid)
// saga 内部会自动调用 next()
yield put({type:action.type.replace('_async',''),user:data})
}
function* getNewIQ(){
}
function* init(){
yield takeLatest('update_user_async',getUser)
yield takeLatest('get_newid_async',getNewIQ)
}
export default init
利用中间件调式Redux程序
- 在谷歌应用商店下载
redux-devtools
安装redux-devtools-extension
npm install -save-dev redux-devtools-extension
- 引入并使用
// 单独使用
import {composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(rootReducer,composeWithDevTools());
export default store;
// 与saga一起使用
import {createStore,applyMiddleware,compose} from 'redux'
import rootSaga from './rootSaga.js';
const sagaMiddleware = createSagaMiddleware();
let enhancer = composeWithDevTools(applyMiddleware(sagaMiddleware))
// 或使用compose
// let enhancer = compose(applyMiddleware(sagaMiddleware),composeWithDevTools())
const store = createStore(rootReducer,enhancer)
export default store;
- 在Chrome浏览器中调试redux程序