React: A JavaScript Frontend Library Focused on UI#
Redux#
Redux is a state management library used for centralized management of shared state and data across the entire App component.
Store#
import { createStore } from 'redux'
const init = {
value: 'defaultValue'
}
const store = createStore((state = init, action) => {
const { type, data } = action
switch (type) {
case 'add':
return { ...state, num: data }
default:
return state
}
})
export default store
Dispatch Action#
import store from './store'
const actionCreator = (data) => {
return {
type: 'add',
data
}
}
btnClick = () => {
const action = actionCreator(233)
store.dispatch(action)
}
Get Store Value#
import store from './store'
class Home extends React.Component {
componentDidMount() {
this.unSubscribe = store.subscribe(() => {
// Listen for store state changes to update the view
this.setState({})
})
}
componentWillUnmount() {
// Unsubscribe
this.unSubscribe()
}
render() {
return (
<>
<h2> {store.getState().num}</h2>
</>
)
}
}
React-Redux#
React-Redux makes it easier to dispatch actions and access data from the store within components, and data updates automatically re-render the components.
- Provider is a component that wraps the outermost root component, allowing its child components to share state.
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
);
- Sending actions and getting values from the store in functional components.
import React, { memo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
export default memo(function Text() {
const dispatch = useDispatch();
const num = useSelector(state => state.num); // Get value
const btnClick = () => {
// Send action
dispatch({
type: 'add',
num: 10,
});
};
return (
<div>
<h2></h2>
<button onClick={e => btnClick()}>Send Action</button>
</div>
);
});
-
Getting store and dispatching actions in class components ==> connect
Connect can associate components with the store for data sharing.
import React from 'react'
import { connect } from 'react-redux'
class Home extends React.Component {
const btnClick = () => {
this.props.sendAction()
}
<>
<button onClick={this.btnClick}>Dispatch</button>
</>
}
export connect(
// Parameter one: state in redux; Parameter two: component's own props
// Return value: object, which will be passed to the component's props
(state, ownProps) => { // Receive
},
// Parameter one: dispatch function for dispatching actions; Parameter two: own props
// Return value: object, which will be passed to the Home component's props
(dispatch, ownProps) => { // Send
return {
sendAction: () => {
// Use dispatch to send an action
dispatch({
type: 'add',
num: 1
})
}
}
}
)(Home)
Complete Code#
- Store
import { createStore } from 'redux';
const init = {
value: 'default value',
num: 0,
};
function reducer(state = init, action) {
const { type, data } = action;
switch (type) {
case 'add':
return { ...state, num: data };
case 'sub':
return { ...state, num: data };
default:
return state;
}
}
export default createStore(reducer);
- Root Component
import React, { PureComponent } from 'react';
import { Provider } from 'react-redux';
import About from './pages/About';
import Home from './pages/Home';
import store from './store';
export default class App extends PureComponent {
render() {
return (
<Provider store={store}>
<Home ownProps={'Hehehe'} />
<hr />
<About />
</Provider>
);
}
}
- Class Component
import React, { PureComponent } from 'react';
import { connect } from 'react-redux';
class Home extends PureComponent {
btnClick = () => {
this.props.addAction();
};
render() {
return (
<div>
<h2>Home: Class Component</h2>
<button onClick={this.btnClick}>+</button>
<h2>Current Count: {this.props.num}</h2>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => {
return state;
};
const mapDispatchToProps = (dispatch, ownProps) => {
let num = 0;
return {
addAction: () => {
dispatch({
type: 'add',
data: ++num,
});
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
- Functional Component
import React, { memo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
export default memo(function About() {
const dispatch = useDispatch();
const num = useSelector(state => state.num);
const btnClick = () => {
let temNum = num;
dispatch({
type: 'sub',
data: --temNum,
});
};
return (
<div>
<h2>About: Functional Component</h2>
<button onClick={btnClick}>-</button>
<h2>Current Count: {num}</h2>
</div>
);
});
combineReducers(reducers)#
- reducers/todos.js
export default function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([action.text]);
default:
return state;
}
}
- reducers/counter.js
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
- reducers/index.js
import { combineReducers } from 'redux';
import todos from './todos';
import counter from './counter';
export default combineReducers({
todos,
counter,
});
- App.js
import { createStore } from 'redux';
import reducer from './reducers/index';
let store = createStore(reducer);
console.log(store.getState());
// {
// counter: 0,
// todos: []
// }
store.dispatch({
type: 'ADD_TODO',
text: 'Use Redux',
});
console.log(store.getState());
// {
// counter: 0,
// todos: [ 'Use Redux' ]
// }
Mobx#
Store#
import { action, computed, runInAction, makeAutoObservable, flow } from 'mobx';
export class CounterStore {
// Define data
count = 0;
list = [1, 2, 3, 4, 5];
asyncData = 0;
flowData = 'default';
constructor() {
// Make data reactive
makeAutoObservable(this, {
// Can be marked or not
incrementCount: action,
decrementCount: action,
changeFilterList: action,
asyncAdd: action,
asyncFlow: action.bound,
filterList: computed,
});
}
// Define action functions to modify data
incrementCount = () => {
this.count++;
};
decrementCount = () => {
this.count--;
};
changeFilterList = () => {
this.list.push(...[5, 4, 3, 2, 1]);
};
// Asynchronous action (wrapped in runInAction)
asyncAdd = async () => {
await Promise.resolve();
runInAction(() => {
this.incrementCount();
this.asyncData++;
});
};
// Asynchronous action (using flow)
asyncFlow = flow(function* () {
yield console.log(this); // Needs to bind this
const data = yield Promise.resolve('Flow');
this.flowData = data;
});
// Define computed
get filterList() {
return this.list.map(item => item * 2);
}
}
Root Store#
import { CounterStore } from './counter';
class Store {
constructor() {
this.counter = new CounterStore();
}
}
const store = new Store();
export { store };
Store Sharing#
Using context
import React, { createContext } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { store } from './store';
export const Context = createContext();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Context.Provider value={store}>
<App />
</Context.Provider>
);
Using in Components#
import React, { memo, useContext } from 'react';
import { observer } from 'mobx-react-lite';
import { Context } from '../..';
const Counter = () => {
const {
counter: {
count,
changeFilterList,
asyncData,
flowData,
decrementCount,
filterList,
incrementCount,
asyncAdd,
asyncFlow,
},
} = useContext(Context);
return (
<div>
<button onClick={incrementCount}>+</button>
<span>{count}</span>
<button onClick={decrementCount}>-</button>
{filterList.map((i, n) => {
return <li key={n}>{i}</li>;
})}
<button onClick={changeFilterList}>Add Array</button>
<hr />
<span>{asyncData}</span>
<button onClick={asyncAdd}>Asynchronous runInAction</button>
<hr />
<span>{flowData}</span>
<button onClick={asyncFlow}>Asynchronous flow</button>
</div>
);
};
export default memo(observer(Counter));