react基础04-redux、react-redux、纯函数和高阶函数、serve
antd基本使用
1、安装:npm i antd
2、引入和使用
import React, { Component } from 'react' import { Button, DatePicker } from 'antd' import { WechatOutlined, WeiboOutlined, SearchOutlined } from '@ant-design/icons' const { RangePicker } = DatePicker export default class App extends Component { render() { return ( <div> App <button>点我</button> <Button type="primary">按钮1</Button> <Button>按钮2</Button> <Button type="link">按钮3</Button> <Button type="primary" icon={<SearchOutlined />}> Search </Button> <WechatOutlined /> <WeiboOutlined /> <DatePicker /> <RangePicker /> </div> ) } }
3、引入样式表
import 'antd/dist/antd.css'
antd样式的按需引入
1、安装插件
npm i react-app-rewired customize-cra babel-plugin-import
2、根目录下创建config-overrides.js
const { override, fixBabelImports } = require('customize-cra') module.exports = override( fixBabelImports('import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }) )
3、package.json修改启动命令
"start": "set BROWSER=none&& react-script start", "build": "react-script build", "test": "react-script test",
改为
"start": "set BROWSER=none&& react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test",
4、此时不需要再引入 antd.css 了,直接引入组件即可,重启后,antd组件的js和css都会按需加载
import { Button, DatePicker } from 'antd'
redux
redux是什么
1、redux是一个专门用于状态管理的库,不是react的插件库
2、它可以用在react、vue、angular等项目中,但基本与react配合使用
3、作用:集中式管理react应用中多个组件共享的状态
什么情况下需要使用redux
1、某个组件的状态,需要让其他组件可以随时拿到
2、一个组件需要改变另一个组件的状态(非父子组件通信)
3、总体原则:能不用就不用,组件间传值比较繁琐时考虑使用
redux工作流程
redux的三个核心概念
1、action
同步action,返回一个对象,包含2个属性:type、data 例:{ type: 'add', data: 10 } 这个data可以是任意数据类型
异步action,返回一个函数,函数中第一个参数为dispatch,函数体中执行异步任务
2、reducer
用于初始化状态,加工状态
加工时,根据旧的state和action,产生新的state的纯函数
3、store
将state、action、reducer联系在一起的对象
如何得到store对象?
import { createStore } from 'redux'
const store = createStore(xxxReducer)
如何使用?
store.getState() // 得到state
store.dispatch() // 派发同步或异步action,也可以直接派发reducer
store.subscribe(被监听的) // 注册监听,当产生了新的state时自动调用
redux的核心api
createStore() // 创建包含指定reducer的store对象
applyMiddleware() // 使用基于redux的中间件
combineReducer() // 合并多个reducer函数
redux使用
简洁版:
1、src/redux/store.js
import { createStore } from 'redux' const countReducer = (prevState = 10, action) => { const { type, data } = action switch (type) { case 'increment': return prevState + data case 'decrement': return prevState - data default: return prevState } } export default createStore(countReducer)
2、components/Count/index.jsx
import React, { Component } from 'react' import store from '../../redux/store' export default class Count extends Component { componentDidMount() { // 监测redux中状态的变化,一旦变化,就调用render store.subscribe(() => this.setState({})) // 通过this.setState({})触发render函数,传{}表示什么也不改,纯粹为了刷新 } render() { return ( <div> {/* 展示redux中的状态 */} <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <br /> <button onClick={() => store.dispatch({ type: 'increment', data: +this.selectNumber.value })} {/*触发redux更新*/} > + </button> <br /> <button onClick={() => store.dispatch({ type: 'decrement', data: +this.selectNumber.value })}> - </button> </div> ) } }
3、可以将检测redux的逻辑放在入口文件中:src/index.js
import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store' ReactDOM.render(<App />, document.querySelector('#root')) store.subscribe(() => ReactDOM.render(<App />, document.querySelector('#root')))
只要redux中的状态发生变化,App组件会重新render。有diff算法的加持,效率没问题。好处是不用在每个组件中通过this.setState()去触发当前组件的render函数
完整版:(使用redux推荐的action机制)
1、src/redux/store.js
import { createStore } from 'redux' const INCREMENT = 'increment', DECREMENT = 'decrement' // 通过action对象去触发。实质是将{ type: 'increment', data: +this.selectNumber.value }在store中抽成函数(createIncrementAction),在触发reducer时调用createIncrementAction函数即可 export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) const countReducer = (prevState = 10, action) => { const { type, data } = action switch (type) { case INCREMENT: return prevState + data case DECREMENT: return prevState - data default: return prevState } } export default createStore(countReducer)
2、components/Count/index.jsx
import React, { Component } from 'react' import store, { createIncrementAction, createDecrementAction } from '../../redux/store' export default class Count extends Component { render() { return ( <div> {/* 展示redux中的状态 */} <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <br /> <button onClick={() => // store.dispatch({type: 'increment',data: +this.selectNumber.value}) store.dispatch(createIncrementAction(+this.selectNumber.value)) } // 触发redux更新 > + </button> <br /> <button onClick={() => // store.dispatch({type: 'decrement',data: +this.selectNumber.value}) store.dispatch(createDecrementAction(+this.selectNumber.value)) } > - </button> </div> ) } }
异步action的使用
1、安装
npm i redux-thunk
2、src/redux/store.js
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' // 用于支持异步action const INCREMENT = 'increment', DECREMENT = 'decrement' // 通过action对象去触发。实质是将{ type: 'increment', data: +this.selectNumber.value }在store中抽成函数(createIncrementAction),在触发reducer时调用createIncrementAction函数即可 export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) // 异步action 函数中一般都会调用同步action export const createIncrementAsyncAction = (data, time) => (dispatch) => setTimeout(() => dispatch(createIncrementAction(data)), time) const countReducer = (prevState = 10, action) => { const { type, data } = action switch (type) { case INCREMENT: return prevState + data case DECREMENT: return prevState - data default: return prevState } } export default createStore(countReducer, applyMiddleware(thunk))
3、components/Count/index.jsx
import React, { Component } from 'react' import store, { createIncrementAsyncAction } from '../../redux/store' export default class Count extends Component { render() { return ( <div> {/* 展示redux中的状态 */} <h1>当前求和为:{store.getState()}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <br /> <button onClick={() => store.dispatch(createIncrementAsyncAction(+this.selectNumber.value, 500))}> 异步加 </button> </div> ) } }
react-redux
react的插件库,简化redux的使用
react-redux将所有组件分为两大类
UI组件:
只负责ui的呈现,不带有任何业务逻辑
通过props接收数据(一般数据和函数)
不使用任何redux的api
一般保存在components文件夹下
容器组件:
负责管理数据和业务逻辑,不负责ui呈现
使用redux的api
一般保存在containers文件夹下
使用:
1、安装:npm i react-redux
2、src/redux/store.js
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' // 用于支持异步action const INCREMENT = 'increment', DECREMENT = 'decrement' export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) // 异步action export const createIncrementAsyncAction = (data, time) => (dispatch) => setTimeout(() => dispatch(createIncrementAction(data)), time) const countReducer = (prevState = 10, action) => { const { type, data } = action switch (type) { case INCREMENT: return prevState + data case DECREMENT: return prevState - data default: return prevState } } export default createStore(countReducer, applyMiddleware(thunk))
3、入口文件:src/index.js
import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store' import { Provider } from 'react-redux' // 通过Provider将store根据需要精准地将store传递给每个组件,这样就不需要在App组件中给每个组件都传递store={store} ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector('#root') )
4、src/containers/Count/index.jsx【react-redux核心:connect】
import React, { Component } from 'react' import { connect } from 'react-redux' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/store' class Count extends Component { render() { const { count, increment, decrement, incrementAsync } = this.props console.log(this.props) return ( <div> <h1>当前求和为:{count}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <br /> <button onClick={() => increment(+this.selectNumber.value)}>+</button> <br /> <button onClick={() => decrement(+this.selectNumber.value)}>-</button> <br /> <button onClick={() => incrementAsync(+this.selectNumber.value, 500)}> 异步加 </button> </div> ) } } /* connect 1、容器组件通过connect传递给UI组件两个值: ①redux中保存的状态 ②用于操作redux状态的方法 2、由react-redux处理,函数第一个函数可以收到state;第二个函数可以收到dispatch。这里不需要再引入store通过store.getState()获取state,也不需要通过store.dispath()去派发,直接用react-redux处理过的state和dispatch就行 3、包裹UI组件,返回容器组件 */ export default connect( (state) => ({ count: state }), // 第一个参数:mapStateToProps --> 映射redux中的state变成CountUI组件的props // 第二个参数:mapDispatchToProps --> 映射redux中的dispatch变成CountUI组件的props,并且不需要subscribe监听触发render函数进行更新,connect会自动触发视图更新 /* // 一般写法(函数) (dispatch) => ({ increment: (number) => dispatch(createIncrementAction(number)), decrement: (number) => dispatch(createDecrementAction(number)), incrementAsync: (number, time) => dispatch(createIncrementAsyncAction(number, time)) }) */ // 简写(对象) { increment: createIncrementAction, // react-redux会自动触发dispatch decrement: createDecrementAction, incrementAsync: createIncrementAsyncAction } )(Count)
5、App.jsx
import React, { Component } from 'react' import Count from './containers/Count' export default class App extends Component { render() { return ( <> <Count /> </> ) } }
多个组件共享redux
1、src/redux/store.js:通过combineReducers汇总reducer(类似vuex中的modules)
import { createStore, applyMiddleware, combineReducers } from 'redux' import thunk from 'redux-thunk' // 用于支持异步action const INCREMENT = 'increment', DECREMENT = 'decrement', ADD_PERSON = 'add_person' export const createIncrementAction = (data) => ({ type: INCREMENT, data }) export const createDecrementAction = (data) => ({ type: DECREMENT, data }) // 异步action export const createIncrementAsyncAction = (data, time) => (dispatch) => setTimeout(() => dispatch(createIncrementAction(data)), time) export const createAddPersonAction = (data) => ({ type: ADD_PERSON, data }) const countReducer = (prevState = 10, action) => { const { type, data } = action switch (type) { case INCREMENT: return prevState + data case DECREMENT: return prevState - data default: return prevState } } const initPerson = [{ id: '001', name: '小明', age: 18 }] const personReducer = (prevState = initPerson, action) => { // 必须是一个纯函数 const { type, data } = action switch (type) { case ADD_PERSON: return [...prevState, data] // prevState.push(data); return prevState 将prevState传入,redux会更新数据,但不会更新视图。因为纯函数中不允许编辑prevState和action,而prevState.push()会更改prevState default: return prevState } } const reducers = combineReducers({ count: countReducer, personList: personReducer }) export default createStore(reducers, applyMiddleware(thunk))
2、src/index.js
import React from 'react' import ReactDOM from 'react-dom' import App from './App' import store from './redux/store' import { Provider } from 'react-redux' ReactDOM.render( // Provider包裹App组件,让App组件的后代容器组件都能接收到store <Provider store={store}> <App /> </Provider>, document.querySelector('#root') )
3、src/containers/Count/index.jsx
import React, { Component } from 'react' import { connect } from 'react-redux' import { createIncrementAction, createDecrementAction, createIncrementAsyncAction } from '../../redux/store' class Count extends Component { render() { const { count, increment, decrement, incrementAsync, personLength } = this.props console.log(this.props) return ( <div> <h1>当前求和为:{count}</h1> <h1>Person组件长度:{personLength}</h1> <select ref={(c) => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <br /> <button onClick={() => increment(+this.selectNumber.value)}>+</button> <br /> <button onClick={() => decrement(+this.selectNumber.value)}>-</button> <br /> <button onClick={() => incrementAsync(+this.selectNumber.value, 500)}> 异步加 </button> </div> ) } } export default connect( (state) => ({ count: state.count, personLength: state.personList.length }), { increment: createIncrementAction, decrement: createDecrementAction, incrementAsync: createIncrementAsyncAction } )(Count)
4、src/containers/Person/index.jsx
import React, { Component } from 'react' import { nanoid } from 'nanoid' // 需安装nanoid import { connect } from 'react-redux' import { createAddPersonAction } from '../../redux/store' class Person extends Component { render() { console.log(this.props) const { count, personList, createAddPersonAction } = this.props return ( <div> <h2>Person组件</h2> <b>Count组件的值:{count}</b> <br /> <input ref={(c) => (this.name = c)} type="text" placeholder="名字" /> <input ref={(c) => (this.age = c)} type="text" placeholder="年龄" /> <button onClick={() => { const name = this.name.value, age = this.age.value, obj = { id: nanoid(), name, age } createAddPersonAction(obj) this.name.value = '' this.age.value = '' }} > 添加 </button> <ul> {personList.map(({ id, name, age }) => ( <li key={id}> {name}---{age} </li> ))} </ul> </div> ) } } export default connect(({ personList, count }) => ({ personList, count }), { createAddPersonAction })(Person)
5、App.js
import React, { Component } from 'react' import Count from './containers/Count' import Person from './containers/Person' export default class App extends Component { render() { return ( <> <Count /> <hr /> <Person /> </> ) } }
6、效果
纯函数和高阶函数
纯函数
1、一种特别的函数,只要是同样的输入(实参),必定得到同样的输出(返回)
2、必须遵守以下一些约束:
①不得改写参数数据
②不会产生任何副作用,例如:网络请求、输入和输出设备
③不能调用Date.now()或Math.rendom()等不纯的方法
3、redux的reducer函数必须是一个纯函数,根据不同的action对象,分别对状态进行不同的处理,这里不能出现任何的DOM操作、ajax请求、计时器等不纯的方法
高阶函数
1、一种特别的函数,参数是函数或者返回值是函数
2、常见的高阶函数:
①定时器中的函数
②数组的forEach、map、filter、reduce、find等方法,bind ……
③promise
④react-redux中的connect函数
3、做用:能实现更加动态,更加可扩展的功能
redux调试工具的使用
1、谷歌浏览器中上传redux开发工具
2、安装插件:npm i redux-devtools-extension
3、store.js
import { composeWithDevTools } from 'redux-devtools-extension' export default createStore( reducers, composeWithDevTools(applyMiddleware(thunk)) // 如果不需要支持异步action就直接传入composeWithDevTools() )
4、效果
serve
1、npm i serve -g
2、打包当前项目:npm run build
3、执行serve build(将build作为服务器的根路径启动一台服务器)