react-redux使用及实现、router简介
资源:
React中文网:https://react.docschina.org/
React Redux API:https://www.redux.org.cn/docs/react-redux/api.html
react-redux github:https://github.com/reduxjs/react-redux
react-router英文文档:https://reactrouter.com/web/guides/quick-start
react-router中文文档:http://react-router.docschina.org/
1、使用react-redux
npm install react-redux
提供了两个api
- Provider 为后代组件提供store
- connect 为组件提供数据和变更方法
API
(1)<Provide store>
<Provider store={store}>
<App />
</Provider>
<Provide store>
使组件层级中的 connect()
方法都能够获得 Redux store。正常情况下,你的根组件应该嵌套在<Provide>
中才能使用 connect()
方法。
(2)connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
// 参数1
const mapStateToProps = state => ({count: state})
// 参数2
const mapDispatchToProps = {
add: () => ({ type: "add" }),
minus: () => ({ type: "minus" });
};
export default connect(
mapStateToProps, //状态映射 mapStateToProps
mapDispatchToProps, //派发事件映射
)(组件);
第二个参数为函数的情况
import {bindActionCreators} from "redux";
(dispatch) => {
let res = {
add: () => ({ type: "ADD" }),
minus: () => ({ type: "MINUS" }),
};
res = bindActionCreators(res, dispatch);
let aadd = () => {
setTimeout(() => dispatch({ type: "ADD" }), 1000);
};
return { dispatch, aadd, ...res };
}
连接 React 组件与 Redux store。
返回一个新的已与 Redux store 连接的组件类。
参数 1
- 函数:mapStateToProps(state, [ownProps])
该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
如果定义该参数,组件将会监听 Redux store 的变化,否则不监听。
ownProps
是当前组件自身的props,如果指定了,那么只要组件接收到新的props,mapStateToProps
就会被调用,mapStateToProps 都会被重新计算,mapDispatchToProps 也会被调用。注意性能!
参数 2
- 对象或函数:mapDispatchToProps(dispatch, [ownProps])
如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。
如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中 dispatch方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。
如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象。
ownProps是当前组件自身的props,如果指定了,那么只要组件接收到新的 props,mapDispatchToProps 就会被调用。注意性能!
参数 3
- 函数:mergeProps(stateProps, dispatchProps, ownProps)
如果指定了这个参数,mapStateToProps() 与 mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据, 或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
具体使用
全局提供store,index.js
import React from "react";
import ReactDOM from "react-dom";
import {Provider} from "react-redux";
import store from "./store/";
// 把Provider放在根组件外层,使用组件能获得store
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
获取状态数据,ReactReduxPage.js
import React, { Component } from "react";
import { connect } from "react-redux";
class ReactReduxPage extends Component {
render() {
const { num, add, minus, asyAdd } = this.props;
return (
<div>
<h1>ReactReduxPage</h1>
<p>{num}</p>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
num: state,
};
};
const mapDispatchToProps = {
add: () => {
return { type: "add" };
},
minus: () => {
return { type: "minus" };
},
};
export default connect(
mapStateToProps, //状态映射 mapStateToProps
mapDispatchToProps //派发事件映射
)(ReactReduxPage);
connect中的参数:state映射和事件映射
详细使用
import React, { Component } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
// connect帮组子组件与store链接,其实就是高阶组件,这里返回的是一个新的组件
export default connect(
// 1.0、mapStateToProps Function (state, ownProps)
(state) => ({ count: state }),
// !谨慎使用ownProps,如果它发生变化,mapStateToProps就会执行,里面的state会被重新计算,容易影响性能
// 1.1、(state, ownProps) => {
// console.log("ownProps", ownProps); //sylog
// return {
// count: state
// };
// }
// 2.0、Object mapDispatchToProps 如果不定义 默认把props注入组件
// 如果是对象的话,原版的dispatch就没有被注入了
// {
// add: () => ({type: "ADD"})
// }
// 2.1、 Function (dispatch,ownProps)
// !谨慎使用ownProps,如果它发生变化,mapDispatchToProps就会执行,容易影响性能
// (dispatch, ownProps) => {
// console.log("ownProps", ownProps);
// } //sylog
(dispatch) => {
let res = {
add: () => ({ type: "ADD" }),
minus: () => ({ type: "MINUS" }),
};
res = bindActionCreators(res, dispatch);
let aadd = () => {
setTimeout(() => dispatch({ type: "ADD" }), 1000);
};
return { dispatch, aadd, ...res };
},
// 3、mergeProps Function
// 如果指定了这个参数,`mapStateToProps()` 与`mapDispatchToProps()` 的执行结果和组件自身的`props` 将传入到这个回调函数中。
(stateProps, dispatchProps, ownProps) => {
console.log("mergeProps", stateProps, dispatchProps, ownProps); //sy-log
return { omg: "omg", ...stateProps, ...dispatchProps, ...ownProps };
}
)(
class ReactReduxPage extends Component {
render() {
console.log("props", this.props); //sylog
const { count, dispatch, add, minus } = this.props;
return (
<div>
<h3>ReactReduxPage</h3>
<p>{count}</p>
<button onClick={() => dispatch({ type: "ADD" })}>
add use dispatch
</button>
<button onClick={add}>add</button>
<button onClick={minus}>minus</button>
<button onClick={this.props.aadd}>1秒后+1</button>
</div>
);
}
}
);
2、实现react-redux
编写Xreact-redux.js ,实现connect、Provider、bindActionCreator
import React, { Component } from "react";
// 传递store
const ValueContext = React.createContext();
// connect
export const connect = (
mapStateToProps = (state) => state,
mapDispatchToProps
) => (WrappedComponent) => {
return class extends Component {
static contextType = ValueContext;
constructor(props) {
super(props);
this.state = {
props: {},
};
}
componentDidMount() {
this.update();
// 拿到store的订阅函数
const { subscribe } = this.context;
subscribe(() => {
this.update();
});
}
update = () => {
const { getState, dispatch } = this.context;
/*(state) => ({ count: state })*/
const stateProps = mapStateToProps(getState());
let dispatchProps;
console.log("mapDispatchToProps", mapDispatchToProps); //sy-log
// 当dispatch是对象时,需要绑定dispatch
// { add: () => ({type: "ADD"}) }
if (typeof mapDispatchToProps === "object") {
dispatchProps = bindActionCreators(mapDispatchToProps, dispatch);
} else if (typeof mapDispatchToProps === "function") {
// 当dispatch是函数时
/**
(dispatch) => {
let res = {
add: () => ({ type: "ADD" }),
minus: () => ({ type: "MINUS" }),
};
res = bindActionCreators(res, dispatch);
return { ...res };
}
*/
dispatchProps = mapDispatchToProps(dispatch, this.props);
} else {
dispatchProps = { dispatch };
}
this.setState({
props: {
...stateProps,
...dispatchProps,
},
});
};
render() {
return <WrappedComponent {...this.props} {...this.state.props} />;
}
};
};
// Provider
// /context
export class Provider extends Component {
render() {
return (
<ValueContext.Provider value={this.props.store}>
{this.props.children}
</ValueContext.Provider>
);
}
}
// let creators = {
// add: () => ({type: "ADD"}),
// minus: () => ({type: "MINUS"})
// };
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args));
}
export function bindActionCreators(creators, dispatch) {
const obj = {};
for (const key in creators) {
obj[key] = bindActionCreator(creators[key], dispatch);
}
return obj;
}
3、react-router简介
react-router包含3个库,react-router、react-router-dom和 react-router-native。
react-router提供最基本的路由功能, 实际使用的时候我们不会直接安装react-router,而是根据应用运行的环境选择安装react-router-dom(在浏览器中使用)或react-router-native(在rn中使用)。
react-router-dom和react-router-native都依赖react-router,所以在安装时,react-router也会自动安装,创建web应用。
安装
npm install --save react-router-dom
基本使用
react-router中奉行一切皆组件的思想,路由器-Router
、链接-Link
、路由-Route
、独占-Switch
、重定向-Redirect
都以组件形式存在
创建RouterPage.js
import React, { Component } from "react";
import { BrowserRouter, Link, Route } from "react-router-dom";
import HomePage from "./HomePage";
import UserPage from "./UserPage";
export default class RouterPage extends Component {
render() {
return (
<div>
<h1>RouterPage</h1>
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
</nav>
{/* 根路由要添加exact,实现精确匹配 */}
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
</BrowserRouter>
</div>
);
}
}
Route渲染内容的三种方式
Route渲染优先级:children>component>render。
三者能接收到同样的[route props],包括match, location and history。
这三种方式互斥,你只能用一种 。
children:函数
有时候,不管location是否匹配,你都需要渲染一些内容,这时候你可以用children。
除了不管location是否匹配都会被渲染之外,其它工作方法与 render完全一样。
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
function ListItemLink({ to, name, ...rest }) {
return (
<Route
path={to}
children={({ match }) => (
<li className={match ? "active" : ""}>
<Link to={to} {...rest}>
{name}
</Link>
</li>
)}
/>
);
}
export default class RouteChildren extends Component {
render() {
return (
<div>
<h3>RouteChildren</h3>
<Router>
<ul>
<ListItemLink to="/somewhere" name="链接1" />
<ListItemLink to="/somewhere-else" name="链接2" />
</ul>
</Router>
</div>
);
}
}
render:函数
但是当你用render的时候,你调用的只是个函数。但是它和 component一样,能访问到所有的[route props]。
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
// 方便的内联渲染
ReactDOM.render(
<Router>
<Route path="/home" render={() => <div>Home</div>} />
</Router>,
node
);
// wrapping/composing
//把route参数传递给你的组件
function FadingRoute({ component: Component, ...rest }) {
return (
<Route {...rest} render={(routeProps) => <Component {...routeProps} />} />
);
}
ReactDOM.render(
<Router>
<FadingRoute path="/cool" component={Something} />
</Router>,
node
);
component: component
只在当location匹配的时候渲染。
import React, { Component, useEffect } from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
export default class RouteComponePage extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
const { count } = this.state;
return (
<div>
<h3>RouteComponePage</h3>
<button onClick={() => { this.setState({ count: count + 1 }); }}>
click change count {count}
</button>
<Router>
{/* 渲染component的时候会调用
React.createElement,如果使用下面这种匿名函数的形
式,每次都会生成一个新的匿名的函数,
导致生成的组件的type总是不相同,这个时候会
产生重复的卸载和挂载 */}
{/* 错误举例 观察下child的didMount和willUnmount函数,会一直挂载卸载 */}
{/* <Route component={() => <Child count={count} />} /> */}
{/* <Route component={() => <FunctionChild count={count} />} /> */}
{/* 下面才是正确的示范 */}
{/* <Route render={() => <Child count={count} />} /> */}
<Route render={() => <FunctionChild count={count} />} />
{/* children 呢 */}
{/* <Route children={() => <Child count={count} />} /> */}
<Route children={() => <FunctionChild count={count} />} />
</Router>
</div>
);
}
}
class Child extends Component {
componentDidMount() {
console.log("componentDidMount"); //sy-log
}
componentWillUnmount() {
console.log("componentWillUnmount"); //sylog
}
render() {
return <div>child-{this.props.count}</div>;
}
}
// hook的例子
function FunctionChild(props) {
useEffect(() => {
return () => {
console.log("WillUnmount"); //sy-log
};
}, []);
return <div>child-{props.count}</div>;
}
注意
当你用component
的时候,Router会用你指定的组件和 React.createElement创建一个新的[React element]。这意味着当你提供的是一个内联函数的时候,每次render都会创建一个新的组件。这会导致不再更新已经现有组件,而是直接卸载然后再去挂载一个新的组件。因此,当用到内联函数的内联渲染时,请使用render或者children。
使用Router
动态路由
使用:id的形式定义动态路由
<Route path="/search/:id" component={Search} />
添加导航链接:
<Link to={"/search/" + searchId}>搜索</Link>
创建Search组件并获取参数
import React, { Component } from "react";
import { BrowserRouter, Link, Route } from "react-router-dom";
import HomePage from "./HomePage";
import UserPage from "./UserPage";
function Search({ match, history, location }) {
const { id } = match.params;
return (
<div>
<h1>Search: {id}</h1>
</div>
);
}
export default class RouterPage extends Component {
render() {
const searchId = "1234";
return (
<div>
<h1>RouterPage</h1>
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
<Link to={"/search/" + searchId}>搜 索</Link>
</nav>
{/* 根路由要添加exact,实现精确匹配 */}
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
<Route path="/search/:id" component={SearchComponent} />
</BrowserRouter>
</div>
);
}
}
嵌套路由
Route组件嵌套在其他页面组件中就产生了嵌套关系
修改Search,添加新增和详情
function DetailComponent(props) {
return <div>DetailComponent</div>;
}
function SearchComponent(props) {
console.log("props", props); //sy-log
const { id } = props.match.params;
return (
<div>
<h3>SearchComponent</h3>
<p>{id}</p>
<Link to={"/search/" + id + "/detail"}>详 情</Link>
<Route path="/search/:id/detail" component={DetailComponent} />
</div>
);
}
404页面
设定一个没有path的路由在路由列表最后面,表示一定匹配 。
{/* 添加Switch表示仅匹配一个*/}
<Switch>
{/* 根路由要添加exact,实现精确匹配 */}
<Route exact path="/" component={HomePage} />
<Route path="/user" component={UserPage} />
<Route path="/search/:id" component={Search} />
<Route render={() => <h1>404</h1>} />
</Switch>;
路由守卫
思路:创建高阶组件包装Route使其具有权限判断功能
创建PrivateRoute
import React, { Component } from "react";
import { Route, Redirect } from "react-routerdom";
export default class PrivateRoute extends Component {
render() {
const { isLogin, path, component } = this.props;
if (isLogin) {
// 登录
return <Route path={path} component={component} />;
} else {
// 去登录,跳转登录页面
return (
<Redirect to={{ pathname: "/login", state: { redirect: path } }} />
);
}
}
}
创建LoginPage.js
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class LoginPage extends Component {
render() {
const { isLogin, location } = this.props;
const { redirect = "/" } = location.state || {};
console.log("props", this.props); //sy-log
if (isLogin) {
// 已经登录
return <Redirect to={redirect} />;
} else {
return (
<div>
<h3>LoginPage</h3>
<button onClick={() => {}}>click</button>
</div>
);
}
}
}
在RouterPage.js配置路由,RouterPage ,访问其他页面先进入PrivateRoute
<Route exact path="/login" component={LoginPage} />
<PrivateRoute path="/user" component={UserPage}/>
1
1
1
1
1
1
1
1
1
1