跳到主要内容

React:数据流

· 阅读需 12 分钟

props和State

组件的props,可以理解为组件的参数,组件接收参数后,返回一个用来描述页面内容的虚拟DOM。

虚拟DOM其实就是一个对象,用来描述真实DOM的,那么在开发过程中,我们是使用JSX来完成虚拟DOM的构建。比如:

import React from "react";
class Comm extends React.Component {


componentDidMount() {
console.log('componentDidMount')
}

shouldComponentUpdate() {
console.log("shouldComponentUpdate")
return true
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
componentWillUnmount() {
console.log("componentWillUnmount")
}
changeText() {
this.setState({
text: new Date().getTime()
})
}
render() {
return <React.Fragment>
<button onClick={this.changeText}>按钮</button>
</React.Fragment>
}
}

export default Comm;

在React组价中,Props可以是从父组件向子组件传递。这个很好理解,因为子组件是在父组件中调用的,在调用子组件的时候,传递的参数自然是向子组件传递了。

比如:

import React from "react";

class Comm extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('componentDidMount')
}

shouldComponentUpdate() {
console.log("shouldComponentUpdate")
return true
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
componentWillUnmount() {
console.log("componentWillUnmount")
}
changeText = () => {
this.setState({
data: new Date().getTime()
})
}
state = {
data: 9
}
render() {
return <React.Fragment>
<Children data={this.state.data} />
<button onClick={this.changeText}>按钮</button>
</React.Fragment>
}
}

function Children({ data }) {
return <div>
<div>这是子组件的:{data}</div>
</div>
}

这其中的props是不可变的,然而React的核心理念就是通过数据驱动UI渲染。所以想要更新组件,只能通过状态state。

React把组件看成是一个状态机,通过和用户的交互,实现不同的状态,然后渲染UI,让用户界面和数据保持一致,所以组件的状态更新后,React就重新渲染对应的组件,不用操作DOM。

如上面的代码中,就是通过修改state来更新组件的,在修改state的时候,使用使用React组件内置的方法setState,接收一个对象,这个对象的key就是需要更新的state,对象的值就是state更新后的值。

如果setState接收的是一个函数,那么这个函数有两个参数,第一个参数是当前的state,第二个参数是当前的props。比如:

 this.setState((state, props) => {
console.log(state, props)
return {
data: new Date().getTime()
}
})

注意几点:

  1. 在更新state的时候,不能直接修改state,因为直接修改,实际上是给组件的State重新赋值,不能触发组件渲染;
  2. 更新State是异步的,那么setState的时候,可能不会立即修改state的值,所以在更新state的时候不要依赖前一个state来对下一个state的更改,比如有这样的代码:
    changeText = () => {
this.setState({
data: this.state.data + 2
})
this.setState({
data: this.state.data + 2
})
this.setState({
data: this.state.data + 2
})
}

对state进行3次连续更新,但是实际上是只进行一次更新运算。因为在同一个周期内进行多次加2的操作,相当于:

let obj = { data: 0 };
const sss = Object.assign({}, { data: obj.data + 2 }, { data: obj.data + 2 }, { data: obj.data + 2 })

最后的sss的结果是{data:2}

所以遇到这样的场景的时候,我们可以向setState函数传入一个函数,在函数来处理之前的异步结果:

changeText = () => {
this.setState((state) => {
return {
data: state.data + 2
}
})
this.setState((state) => {
return {
data: state.data + 2
}
})
this.setState((state) => {
return {
data: state.data + 2
}
})
}
  1. 在调用setState的时候,React会把提供的对象合并到当前的state中,但是只会更新修改的状态,其他状态不变。

组件通信

组件之间的通信是通过props属性来完成,在前面的代码中,我们知道父组件通过props直接向子组件传递数据,组件本身是通过state来维护自身的状态,那么如果子组件想要向父组件传递数据呢?

也是通过props,因为props可以传递函数,那子组件调用父组件传递过来的函数。由于这个函数是在父组件内部定义的,父组件就可以在函数被调用的时候接收并且修改的自身的状态。

现在修改一下前面代码:

function Children({ data ,changeText}) {
return <div>
<div>这是子组件的:{data}
<button onClick={()=>changeText()}>修改</button>
</div>
</div>
}
    render() {
return <React.Fragment>
<p>父组件:{this.state.data}</p>
<Children data={this.state.data} changeText={this.changeText}/>
<button onClick={this.changeText} >按钮</button>
</React.Fragment>
}

现在就可在子组件中修改父组件的数据了。

复杂状态管理的解决方案

前面介绍了组件基本的通信方式,当项目的业务逻辑比较复杂的时候,那么这样的通信方法就不合适了。比如现在有这样的组件结构:

在这里插入图片描述

现在组件E和组件F通信,按照之前的方式,就是组件E把状态传给组件B,组件B再把状态传给组件A,A再传给C,最后C传给组件F。这样组件层级结构简单的话,可以这样做。

如果组件层级结构足够复杂了,就需要写大量的通信逻辑代码,但是整个通信过程中,组件B、A和C仅仅是承担通道的作用。

React提供了一个比较高级的数据管理方式——Context上下文,但是Context也会有一些问题的,比如Context的修改不太方便。

还有一个方法就是Redux,Redux是JavaScript状态容器,提供可预测的状态管理。 一个Redux应用中,所有的State都是以一个对象树的形式存储在唯一的store中。

改变state,只能通过触发action,action是一个描述发生什么事的对象,为了描述action如何改变state,还需要写reducers。

现在先来定义一下store:

import {legacy_createStore,applyMiddleware,compose}  from 'redux';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import reducers from './reducers';
const storageConfig = {
key: "root",
storage,
};
const middleware = [thunk];

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const configStore = persistReducer(storageConfig,reducers);
export let store = legacy_createStore(configStore,composeEnhancers(
applyMiddleware(...middleware)
));
export let persistor = persistStore(store);

这里面包含了数据持久化保存,这个可以不用管。通过createStore方法来创建store的,这里因为版本比较新,用的是legacy_createStore。

在应用的入口文件index.js或者是app.jsx,我这里是index.jsx:

import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { Provider } from 'react-redux';
import { BrowserRouter } from "react-router-dom";
import { store, persistor } from "./store";
const { PersistGate } = require('redux-persist/integration/react')
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<BrowserRouter>
<App />
</BrowserRouter>
</PersistGate>
</Provider>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

使用reac-redux自带的Provider 组件来包裹应用的顶级组件。

reducer代码:

import { combineReducers } from "redux";
import menuReducer from "./menu";
const reducers = combineReducers({
menus: menuReducer,
});

export default reducers

这里使用了combineReducers函数把多个reducer组合起来,现在只有一个menu:

const menuState = [
{
title: "组件",
key: "component",
router: null,
children: [
// { title: "表格", key: 'table', router: "/components/table" },
{ title: "React", key: 'react', router: "/components/react" }
]
},
{
title: "Echarts",
key: "1",
router: null,
children: [
{ title: "dashboard", key: '1-1', router: "/components/dashboard", component: "Dashboard" },
{ title: "柱状图", key: '1-2', router: "/components/barEcharts", component: "Bar" },
{ title: "条形图", key: '1-3', router: "/components/rowBar", component: "RowBar" },
{ title: "扇形图", key: '1-4', router: "/components/radialPolar", component: "RadialPolar" },
{ title: "数学函数", key: '1-5', router: "/components/Sin", component: "Sin" },
{ title: "散点图", key: '1-6', router: "/components/ScatterChart", component: "ScatterChart" },
]
},

{
title: "可视化",
key: "visualization",
router: null,
children: [
{ title: "可视化1", key: 'visualization-1', router: "/app1", component: "Modal" },
]
},

];
export default function menuReducer(state = menuState, action: any) {
switch (action.type) {
case "UPDATE_CONFIG":
return {
config: action.value
};
default:
return state
}
}

在Comm组件中从store中订阅对应的state:

const mapStateToProps = (state) => {
return { menus: state.menus }
}

const mapDispatchToProps = (dispatch) => {
return {
changeMenu: () => dispatch({ type: 'UPDATE_CONFIG', value: [] })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Comm);

在Comm组件的componentDidMount生命周期函数中打印订阅的state,效果如图:

在这里插入图片描述

mapDispatchToProps方法中,将dispatch传到组件中并且,在组件中只要触发对应的dispatch,就可以修改store的state了,Comm组件完整代码:

import React from "react";
import { connect } from "react-redux";
class Comm extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log('componentDidMount', this.props)
}

shouldComponentUpdate() {
console.log("shouldComponentUpdate")
return true
}
componentDidUpdate() {
console.log("componentDidUpdate")
}
componentWillUnmount() {
console.log("componentWillUnmount")
}
changeText = () => {
this.setState((state) => {
return {
data: state.data + 2
}
})
this.setState((state) => {
return {
data: state.data + 2
}
})
this.setState((state) => {
return {
data: state.data + 2
}
})
this.props.changeMenu()
console.log("this.props", this.props)
}
state = {
data: 9
}
render() {
return <React.Fragment>
<p>父组件:{this.state.data}</p>
<Children data={this.state.data} changeText={this.changeText} />
<button onClick={this.changeText} >按钮</button>
</React.Fragment>
}
}

function Children({ data, changeText }) {
return <div>
<div>这是子组件的:{data}
<button onClick={() => changeText()}>修改</button>
</div>
</div>
}
const mapStateToProps = (state) => {
return { menus: state.menus }
}

const mapDispatchToProps = (dispatch) => {
return {
changeMenu: () => dispatch({ type: 'UPDATE_CONFIG', value: [] })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Comm);

这就是组件的数据流。