React系列文章导航
先来看下上一篇文章中,做的项目的相关页面:
诸如这种,我们的Header
组件都是手写的,同时还需要手动引入第三方css
文件bootstrap.css
。
需求:有没有一个已存在的库,可以让我们快速搭建起这样的组件呢,并且里面的样式也不用我们去添加。
那么国内比较流行的ReactUI组件库就是Ant Design,简称antd。
antd官方文档
1.安装对应的antd库:
npm install antd
2.去官网上选择自己想要的样式,比如一个Button按钮,展开代码,引入对应的组件,复制自己想要的样式即可。
3.App
组件:
import React, {
Component } from 'react';
import {
Button } from 'antd';
// 需要我们手动引入,否则样式不会显示
import 'antd/dist/antd.css'
class App extends Component {
render() {
return (
<div>
................<Button type="primary">点击</Button>
<hr/>
</div>
);
}
}
export default App;
上述案例中,有一个瑕疵,请看:
import 'antd/dist/antd.css'
这里我们将antd.css
的所有内容全部引入进来了,其实很多的样式我们并不会用到,因此我们需要按需引入。则需要对create-react-app
的默认配置进行自定义。
解决方案:
1.引入react-app-rewired
来修改package.json
里面的配置,此外,还需要安装customize-cra
。
npm install react-app-rewired customize-cra
2.修改对应的package.json
里面的配置:
/* package.json */
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
3.安装babel-plugin-import
,一个用于按需加载组件代码和样式的 babel 插件
npm install babel-plugin-import
4.在根目录创建一个config-overrides.js
文件(和package.json
文件同目录),用于修改默认配置:
// 配置具体的修改规则
const {
override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
// 需要做按需引入
libraryName: 'antd', // 做antd的按需引入
libraryDirectory: 'es', // antd里面用了es的模块化规范
style: 'css', // 按需引入的是css样式
}),
);
5.删除App.jsx
中原有的样式引入:
import 'antd/dist/antd.css'
6.重启项目后观察样式是否依旧存在,若存在,那么按需引入成功:
自定义主题需要用到less
变量覆盖功能,我们可以引入customize-cra
中提供的less
相关的函数addLessLoader
来帮助加载less
样式,同时修改config-overrides.js
。
1.安装less-loader
(版本太高会报错)以及less
:
npm install less less-loader@7.0.0
2.修改配置:
// 配置具体的修改规则
const {
override, fixBabelImports, addLessLoader } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {
'@primary-color': 'green' },
}),
);
Redux是一个专门用于做状态管理的JS库。用来集中式管理React应用中多个组件共享的状态。
什么情况下需要使用Redux?
1.
type
:标识属性,值为字符串,唯一,必要属性。
2.data
:数据属性,值类型任意,可选属性。
{type:'ADD_STUDENT',data:{name:'tom',age:20}}
Reducer:
state
和action
,产生新的state
的纯函数。Store:
state
、action
、reducer
联系在一起的对象。import React, {
Component } from 'react';
class Count extends Component {
state = {
count: 0 }
// 加法
increment = () => {
const {
value } = this.selectNumber
const {
count } = this.state
this.setState({
count: count + value * 1 })
}
decrement = () => {
const {
value } = this.selectNumber
const {
count } = this.state
this.setState({
count: count - value * 1 })
}
incrementIfOdd = () => {
const {
value } = this.selectNumber
const {
count } = this.state
if (count % 2 !== 0) {
this.setState({
count: count + value * 1 })
}
}
incrementAsync = () => {
const {
value } = this.selectNumber
const {
count } = this.state
setTimeout(() => {
this.setState({
count: count + value * 1 })
}, 500);
}
render() {
return (
<div>
<h1>当前求和为:{
this.state.count}</h1>
<select ref={
c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={
this.increment}>+</button>
<button onClick={
this.decrement}>-</button>
<button onClick={
this.incrementIfOdd}>当前和为奇数时,才能够相加</button>
<button onClick={
this.incrementAsync}>异步加</button>
</div>
);
}
}
export default Count;
App
组件:
import React, {
Component } from 'react';
import Count from './components/Count'
class App extends Component {
render() {
return (
<div>
<Count/>
</div>
);
}
}
export default App;
入口文件:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))
安装redux:
npm install redux
项目结构:
Count
组件:去除了Count
组件中自身的状态。
import React, {
Component } from 'react';
// 引入store,用于获取状态
import store from '../../redux/store'
class Count extends Component {
// 加法
increment = () => {
const {
value } = this.selectNumber
// 通知redux
store.dispatch({
type: 'increment', data: value * 1 })
}
decrement = () => {
const {
value } = this.selectNumber
store.dispatch({
type: 'decrement', data: value * 1 })
}
incrementIfOdd = () => {
const {
value } = this.selectNumber
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch({
type: 'increment', data: value * 1 })
}
}
incrementAsync = () => {
const {
value } = this.selectNumber
setTimeout(() => {
store.dispatch({
type: 'increment', data: value * 1 })
}, 500);
}
render() {
return (
<div>
<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>
<button onClick={
this.increment}>+</button>
<button onClick={
this.decrement}>-</button>
<button onClick={
this.incrementIfOdd}>当前和为奇数时,才能够相加</button>
<button onClick={
this.incrementAsync}>异步加</button>
</div>
);
}
}
export default Count;
store.js
:暴露store对象,并提供许多API。
createStore
函数,来创建一个store
。createStore
函数调用的时候需要传入一个为其服务的reducer
。store
对象暴露出去,export default createStore(xxx)
。/**
* 改文件专门用来暴露一个store对象,整个应用只有一个store对象
*/
// 引入createStore,专门用于创建redux中最为核心的store对象
import {
createStore } from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer)
count_reducer.js
:本质上是一个函数,定义处理逻辑。
reducer
本质是一个函数,接收两个属性:preState
,action
,返回加工后的状态。reducer
有两个作用:初始化状态和加工状态。reducer
被第一次调用的时候,是store
自动触发的,传递的preState
是undefined
。xxx_reducer.js
,表明是哪个组件的reducer
。/**
* 1.该文件是用来创建一个为Count组件服务的Reducer,Reducer的本质就是一个函数
* 2.reducer函数会接收到两个参数,分别是:之前的状态preState,动作对象action
*/
export default function countReducer(preState, action) {
if (preState === undefined) preState = 0
/**
* 1.type:标识属性,值为字符串,唯一,必要属性。
2.data:数据属性,值类型任意,可选属性。
*/
const {
type, data } = action
// 根据type来决定如何加工数据
switch (type) {
case 'increment': // 如果是加
return preState + data
case 'decrement': // 如果是减
return preState - data
default:
return preState
}
}
index.js
:
store
中状态的改变,一旦发生改变重新渲染App
。import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
ReactDOM.render(<App />, document.getElementById('root'))
// 检测redux中状态的变化,只要变化,就调用render重新渲染
store.subscribe(()=>{
ReactDOM.render(<App />, document.getElementById('root'))
})
// 引入actionCreator,专门用于创建action对象
import {
createIncrementAction, createDecrementAction } from '../../redux/count_action'
// 加法
increment = () => {
const {
value } = this.selectNumber
// 通知redux
store.dispatch(createIncrementAction(value * 1))
}
decrement = () => {
const {
value } = this.selectNumber
store.dispatch(createDecrementAction(value * 1))
}
incrementIfOdd = () => {
const {
value } = this.selectNumber
const count = store.getState()
if (count % 2 !== 0) {
store.dispatch(createIncrementAction(value * 1))
}
}
incrementAsync = () => {
const {
value } = this.selectNumber
setTimeout(() => {
store.dispatch(createIncrementAction(value * 1))
}, 500);
}
constant.js
:
/**
* 用于action对象中type类型的常量值
*/
export const INCREMENT ='increment'
export const DECREMENT ='decrement'
count_action.js
:
/**
1. 改文件专门为Count组件生成action对象
*/
import {
INCREMENT, DECREMENT } from './constant'
export const createIncrementAction = data => ({
type: INCREMENT, data })
export const createDecrementAction = data => ({
type: DECREMENT, data })
修改如下:
安装对应组件:
npm install redux-thunk
count_action.js
:增加以下代码
action
就是指action的值为函数,异步action
中一般会调用同步的action
。npm install redux-thunk
,并配置在store
中。action
的函数不再返回一般对象,而是一个函数,该函数中写一个异步任务。action
去真正操作数据。export const createIncrementAsyncAction = (data, time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrementAction(data))
}, time);
}
}
store.js
:
/**
* 改文件专门用来暴露一个store对象,整个应用只有一个store对象
*/
// 引入createStore,专门用于创建redux中最为核心的store对象
import {
applyMiddleware, createStore } from 'redux'
// 引入为Count组件服务的reducer
import countReducer from './count_reducer'
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer, applyMiddleware(thunk))
Count
组件:修改对应异步函数内容。
incrementAsync = () => {
const {
value } = this.selectNumber
store.dispatch(createIncrementAsyncAction(value * 1, 500))
}
基于以上案例,可以对Store、Reducer、Action做一个简单的归纳:
Action
:负责定义对象,包括其type
类型以及数据体内容。 表示动作对象。Reducer
:负责初始化状态,并且根据状态type
的不同,去做对应的逻辑,去修改状态。 返回值是新状态,本质上就是一个函数。Store
:负责通过createStore(Reducer)
来暴露一个Store
对象。props
来传递。前期工作:安装react-redux
npm install react-redux
1.将原本的Count
组件改装成一个UI组件(与Redux没有任何关联)
import React, {
Component } from 'react';
class Count extends Component {
increment = () => {
const {
value } = this.selectNumber
this.props.jia(value * 1)
}
decrement = () => {
const {
value } = this.selectNumber
this.props.jian(value * 1)
}
incrementIfOdd = () => {
const {
value } = this.selectNumber
if (this.props.count % 2 !== 0) {
this.props.jia(value * 1)
}
}
incrementAsync = () => {
const {
value } = this.selectNumber
this.props.jiaAsync(value * 1, 500)
}
render() {
console.log('UI组件接收到的props是', this.props)
// ...
}
}
export default Count;
2.准备一个容器组件Count
:负责与redux交互
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入connect用来连接UI组件和redux
import {
connect } from 'react-redux'
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
// 返回的对象中,key会传递给UI组件中props属性的key,value就是props属性中对应的value,value就是状态
// mapStateToProps用于传递状态
function mapStateToProps(state) {
return {
count: state }
}
// mapDispatchToProps用于传递操作状态的方法
function mapDispatchToProps(dispatch) {
return {
jia: (number) => {
dispatch(createIncrementAction(number))
},
jian: (number) => {
dispatch(createDecrementAction(number))
},
jiaAsync: (number, time) => {
dispatch(createIncrementAsyncAction(number, time))
},
}
}
// 使用connect()()创建并暴露一个容器组件
// 连接store的部分,必须在外层写,也就是父类App组件中写
// 此时会把mapStateToProps和mapDispatchToProps两个对象传递给UI组件
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
App
组件:负责引入对应的store
给容器,这样容器中就可以直接获取state
与dispatch
。
import React, {
Component } from 'react';
import Count from './containers/Count'
import store from './redux/store'
class App extends Component {
render() {
return (
<div>
<Count store={
store} />
</div>
);
}
}
export default App;
做出总结:
connect()()
传递,UI组件则通过props
获取。react-redux
下的connect
函数:// mapStateToProps映射状态,返回值是一个对象
// mapDispatchToProps映射操作状态的方法,返回值是一个对象
export default connect(mapStateToProps, mapDispatchToProps)(UI组件)
store
是通过props
传递进去的,在外层的App
组件中引入。比如mapStateToProps(state)
中的参数就可以直接获取到。优化点1:
App
组件外层对store
进行监听,一有变化我们就会重新渲染组件。store.subscribe(()=>{
ReactDOM.render(<App />, document.getElementById('root'))
})
connect
这个创建容器组件的方法中完成了),删除即可。优化点2:容器组件中,mapStateToProps
和mapDispatchToProps
函数的简写。
优化点3:
App
组件中,对于容器组件,传入一个store
,如:< Count store={store} />
,但是如果我们App
组件中有多个组件,而每个组件都需要传入store
,那怎么办?Provider
,在index.js
入口文件将App
组件包裹起来即可。优化点4:整合UI组件和容器组件。
优化后的项目结构:(可见删除了UI组件)
容器组件Count
:
import React, {
Component } from 'react';
// 引入connect用来连接UI组件和redux
import {
connect } from 'react-redux'
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
class Count extends Component {
increment = () => {
const {
value } = this.selectNumber
this.props.jia(value * 1)
}
decrement = () => {
const {
value } = this.selectNumber
this.props.jian(value * 1)
}
incrementIfOdd = () => {
const {
value } = this.selectNumber
if (this.props.count % 2 !== 0) {
this.props.jia(value * 1)
}
}
incrementAsync = () => {
const {
value } = this.selectNumber
this.props.jiaAsync(value * 1, 500)
}
render() {
console.log('UI组件接收到的props是', this.props)
return (
<div>
<h1>当前求和为:{
this.props.count}</h1>
<select ref={
c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={
this.increment}>+</button>
<button onClick={
this.decrement}>-</button>
<button onClick={
this.incrementIfOdd}>当前和为奇数时,才能够相加</button>
<button onClick={
this.incrementAsync}>异步加</button>
</div>
);
}
}
// 使用connect()()创建并暴露一个容器组件
// 连接store的部分,必须在外层写,也就是父类App组件中写
// 此时会把mapStateToProps和mapDispatchToProps两个对象传递给UI组件
export default connect(
state => ({
count: state }),
{
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction,
}
)(Count)
redux
相关文件都不变。App
组件删除store
引入:
入口文件index.js
增加Provider
:
优化完成~
下一篇文章准备学习数据共享,在本篇文章的基础上,对上述案例做一个最终的完善和发布。