redux:专注于状态管理的库
redux是什么:
- redux专注于状态管理,和react解耦(亦可与vue、angular等联合使用)
- 单一状态,单向数据流
- 核心概念:store, state, action, reducer
redux的功能:
- redux有一个保险箱(store),所有的状态,在这里都有记录(state)
- 需要改变的时候,需要告诉专员(dispatch)要干什么(action)
- 处理变化的人(reducer)拿到state和action,生成新的state,生成后使用新的state重新render页面
redux的正确使用方法
- 首先通过reducer新建store,随时通过store.getState获取状态
- 需要状态变更,store.dispatch(action)来修改状态
- reducer函数接受state和action,返回新的state,可以用store.subscribe监听每次修改
import { createStore } from 'redux'
// 通过reducer修改state
// reducer根据旧state和action,生成新的state
function counter(state=0, action) {
switch(action.type) {
case 'add':
return state + 1
case 'reduce':
return state - 1
default:
return 10
}
}
//使用reducer新建store
const store = createStore(counter)
// 定义监听器,监听store
function listenerStore() {
// 获取所有的state
const current = store.getState()
console.log(`当前state为${current}`)
}
// 订阅监听器
store.subscribe(listenerStore)
//派发事件,传递action
//listenerStore中会打印出11 10
store.dispatch({type: 'add'})
store.dispatch({type: 'reduce'})
redux如何和react结合使用(普通使用)
- 把store.dispatch方法传递给组件,内部可以调用修改状态
- Subscribe订阅render函数,每次修改都重新渲染
- Redux相关内容,移到单独的文件index.redux.js单独管理
更进一步:处理异步、调试工具、更优雅的和react结合 - Redux处理异步,需要redux-thunk插件(中间件)
- npm install redux-devtools-extension并且开启(调试工具)
- 使用react-redux优雅的连接react与redux
如何处理异步:Redux默认只处理同步,异步任务需要react-thunk中间件 - Npm install redux-thunk –save
- 使用applyMiddleware开启thunk中间件
- Action可以返回函数,使用dispatch提交action
调试:chrome扩展程序安装redux - 新建store的时候判断window.devToolsExtension
- 使用compose结合thunk和window.devToolsExtension
- 调试窗的redux选项卡,实时看到state
// index.redux.js文件,存放reducer
const Add = 'add'
const Reduce = 'reduce'
// 通过reducer修改state
// reducer根据旧state和action,生成新的state
export function counter(state=0, action) {
switch(action.type) {
case Add:
return state + 1
case Reduce:
return state - 1
default:
return 10
}
}
export function addFun() {
return {type: Add}
}
export function reduceFun() {
return {type: Reduce}
}
export function asynAdd() {
// 注意return的是一个箭头函数,dispatch参数
return dispatch => {
setTimeout(() => {
dispatch(addFun())
}, 2000)
}
}
// index.js文件,父组件
import React from 'react'
import ReactDom from 'react-dom'
// applyMiddleware用于开启react-thunk, 管理redux中间件
// compose结合thunk和调试工具,实时监控state
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import App from './app'
// 为解耦,应将addFun在此处引用,而不是放在app中,app中应该是个干干净净的组件
import { counter, addFun, reduceFun, asynAdd } from "./index.redux"
// compose(开启thunk, 判断浏览器是否安装了redux调试工具安装则开启否则执行空函数)
const store = createStore(counter, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
))
function render() {
ReactDom.render(
<App
store={store}
addFun={addFun}
reduceFun={reduceFun}
asynAdd={asynAdd} />,
document.getElementById('root')
)
}
render()
//若不订阅render,state发生变化后不会重新渲染render
store.subscribe(render)
// app.js, 子文件
import React from 'react'
class App extends React.Component {
// constructor(props) {
// super(props)
// }
render() {
const store = this.props.store
const num = store.getState()
const addFun = this.props.addFun
const reduceFun = this.props.reduceFun
const asynAdd = this.props.asynAdd
return (
<div>
<h1>当前state为{num}</h1>
<button onClick={() => store.dispatch(addFun())}>增加state</button>
<button onClick={() => store.dispatch(reduceFun())}>减少state</button>
<button onClick={() => store.dispatch(asynAdd())}>异步增加state</button>
</div>
)
}
}
export default App
使用react-redux
- npm install react-redux –save
- 忘记subscribe,记住reducer, action和dispatch即可
- React-redux提供provider和connect两个接口来连接
- provider组件在应用最外层,传入store即可,只用一次
- Connect负责从外部获取组件需要的参数
- Connect可以用装饰器的方式来写
- Npm run eject弹出个性化配置
- npm install babel-plugin-transform-decorators-legacy –save-dev安装装饰器插件
- package.json里babel加上plugins配置
// 使用react-redux重写上面的组件
// index.redux.js不变
// index.js,父组件
import React from 'react'
import ReactDom from 'react-dom'
// applyMiddleware用于开启react-thunk, 管理redux中间件
// compose结合thunk和调试工具,实时监控state
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import App from './app'
import { counter } from "./index.redux"
// compose(开启thunk, 判断浏览器是否安装了redux调试工具安装则开启否则执行空函数)
const store = createStore(counter, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
))
// react-redux管理, provider只使用一次
ReactDom.render(
(<Provider store={store}>
<App />
</Provider>),
document.getElementById('root')
)
// app.js,子组件
import React from 'react'
import { connect } from 'react-redux'
import { addFun, reduceFun, asynAdd } from "./index.redux";
/* 正常写法
const mapStatetoProps = (state) => {
return {num: state}
}
const actionCreators = {addFun, reduceFun, asynAdd}
// 装饰器的方式,connect先执行,再将App当作参数传进去
// connect(你要state什么属性放到props中,你要什么action放到props中并自动dispatch)
App = connect(mapStatetoProps, actionCreators)(App)
*/
// 使用babel-plugin-transform-decorators-legacy装饰器插件的写法,效果同上
// babel-plugin-transform-decorators-legacy插件需安装,并在package.json的babel中进行配置
@connect(
state => ({num: state}),
{addFun, reduceFun, asynAdd}
)
class App extends React.Component {
render() {
return (
<div>
<h1>当前state为{this.props.num}</h1>
<button onClick={this.props.addFun}>增加state</button>
<button onClick={this.props.reduceFun}>减少state</button>
<button onClick={this.props.asynAdd}>异步增加state</button>
</div>
)
}
}
export default App
React-router4:react路由库
- 4是全新的版本,和之前(2)版本不兼容,浏览器和RN均兼容
- React开发单页应用必备,践行路由即组件的概念
- 核心概念:动态路由、route、link、switch
- npm install react-router-dom –save 安装浏览器端的路由
- BrowserRouter,包裹整个应用,只使用一次(路由有BrowserRouter和HashRouter
- Router路由对应渲染的组件,可嵌套
- Link跳转专用
- url参数, Route组件参数可用冒号标识参数
- Redirect组件跳转
- Switch只渲染一个子Route组件
- 复杂redux应用,多个reducer, 用combineReducers合并
import { BrowserRouter, Route, Link, Redirect, Switch } from 'react-router-dom'
function Router2() {
return <h2>Router2</h2>
}
function Router3() {
return <h2>Router3</h2>
}
function RouterParam(props) {
return <h2>带参数的url, 当前router为:{props.match.params.location}</h2>
}
// react-redux管理, provider只使用一次
// Link: 点击跳转到指定路由, Route: 路由对应渲染模板
ReactDom.render(
(<Provider store={store}>
<BrowserRouter>
<div>
<ul>
<li><Link to='/'>router1</Link></li>
<li><Link to='/router2'>router2</Link></li>
<li><Link to='/router3'>router3</Link></li>
</ul>
{/*switch只渲染匹配到的第一个路由*/}
<Switch>
{/*exact代表完全匹配,若省去,会按正则匹配,所有包含的/都会显示此路由下的模块内容*/}
<Route path='/' exact component={App}></Route>
<Route path='/:location' component={RouterParam}></Route>
<Route path='/router2' component={Router2}></Route>
<Route path='/router3' component={Router3}></Route>
</Switch>
{/*强制跳转至指定页面*/}
<Redirect to='/router2'></Redirect>
</div>
</BrowserRouter>
</Provider>),
document.getElementById('root')
)
redux+react-router实战
// index.js: 顶级父组件
import React from 'react'
import ReactDom from 'react-dom'
// applyMiddleware用于开启react-thunk, 管理redux中间件
// compose结合thunk和调试工具,实时监控state
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { Provider } from 'react-redux'
import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'
import reducers from './reducers'
import Auth from './Auth'
import Dashbord from './dashbord'
// compose(开启thunk, 判断浏览器是否安装了redux调试工具安装则开启否则执行空函数)
const store = createStore(reducers, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f => f
))
// react-redux管理, provider只使用一次
// Link: 点击跳转到指定路由, Route: 路由对应渲染模板
ReactDom.render(
(<Provider store={store}>
<BrowserRouter>
{/*switch只渲染匹配到的第一个路由*/}
<Switch>
{/*exact代表完全匹配,若省去,会按正则匹配,所有包含的/都会显示此路由下的模块内容*/}
<Route path='/login' exact component={Auth}></Route>
<Route path='/dashbord' component={Dashbord}></Route>
{/*强制跳转至指定页面*/}
<Redirect to='/dashbord'></Redirect>
</Switch>
</BrowserRouter>
</Provider>),
document.getElementById('root')
)
// Auth.redux.js, 登录状态管理,未登录用户不可查看dashbord页面
const Login = 'login'
const Logout = 'logout'
export function auth(state={isAuth: false, user: 'lmh'}, action) {
switch(action.type) {
case Login:
return {...state, isAuth: true}
case Logout:
return {...state, isAuth: false}
default:
return state
}
}
//action
export function login() {
return {type: Login}
}
export function logout() {
return {type: Logout}
}
// reducers.js, 由于app.js模块与auth.js模块都有reducer状态管理,多处调用不方便,所以在此文件中使用combineReducers合并所有reducer
// combineReducers合并所有reducer, 并且返回
import { combineReducers } from 'redux'
import { counter} from "./index.redux";
import { auth } from "./auth.redux";
export default combineReducers({counter, auth})
// auth.js, 登录页面管理
import React from 'react'
import { connect } from 'react-redux'
import { Redirect } from 'react-router-dom'
import { login } from "./auth.redux";
@connect(
state => state.auth,
{login}
)
class Auth extends React.Component {
render() {
return(
<div>
{this.props.isAuth ? <Redirect to='/dashbord/' /> : null}
<h3>还未登录,请点击登录</h3>
<button onClick={this.props.login}>登录</button>
</div>
)
}
}
export default Auth
// dashbord.js, 导航页面管理
import React from 'react'
import { Link, Route, Redirect } from 'react-router-dom'
import { connect } from 'react-redux'
import App from './app'
import { logout } from "./auth.redux";
function RouterParam(props) {
return <h2>当前路由为:{props.location.pathname}</h2>
}
@connect(
state => state.auth,
{logout}
)
class Dashbord extends React.Component {
render() {
const match = this.props.match
const directToLogin = <Redirect to='/login'></Redirect>
const app = (
<div>
{this.props.isAuth ? <button onClick={this.props.logout}>注销</button> : null}
<ul>
<li><Link to={`${match.url}`}>router1</Link></li>
<li><Link to={`${match.url}/router2`}>router2</Link></li>
<li><Link to={`${match.url}/outer3`}>router3</Link></li>
</ul>
<Route path={`${match.url}`} exact component={App}></Route>
<Route path={`${match.url}/router2`} component={RouterParam}></Route>
<Route path={`${match.url}/outer3`} component={RouterParam}></Route>
</div>
)
return this.props.isAuth ? app : directToLogin
}
}
export default Dashbord
// index.redux.js模块同上
// app.js模块同上