简介:
1、本文是个人课上学习内容的总结和梳理,主要知识点来自于网课以及React官方文档、掘金社区。
2.目前尚处于 React 乃至前端入门阶段,因此很多内容理解的不是很透彻,文章更多是用来学习记录而非干货分享。
React 组件间通信
父子组件通信
若不借鉴第三方库,原生React是只支持单向数据流的:父级可以向子级通过props的方式传递state和callback函数,而子级不能向父级发送事件或者修改父级的props。因此,若子级想要修改父级组件的状态state,只能通过父级传递下来的callback函数进行回调触发。
父组件向子组件通信
父组件:父组件为两个按钮,用来选中切换Item的信息,其中引用子组件。
子组件:使用props属性接收传递来的数据,进行render
类组件
import React from 'react'
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {item:{content:'jsx'}}
};
changeItem(e){
this.setState({item:{content:e}});
};
render(){
let {item} = this.state;
return (
<div>
<button onClick={this.changeItem.bind(this,'jsx')}>item1</button>
<button onClick={this.changeItem.bind(this,'li')}>item2</button>
<Child item={item} />
</div>
)
}
}
class Child extends Component {
render() {
return <div> content: {this.props.item.content} </div>;
}
}
Function组件
import React,{useState} from 'react'
function Parent(){
const [item,setItem] = useState({content:'jsx'})
return (
<div>
<button onClick={() => setItem('jsx')}>item1</button>
<button onClick={() => setItem('li')}>item2</button>
<Child item={item} />
</div>
)
}
function Child(){
render() {
return <div> content: {this.props.item.content} </div>;
}
}
子组件向父组件通信
父组件:接收子组件的callback 函数传递会数据
子组件:两个按钮,用来选中callbackh回调
类组件
import React from 'react'
class Parent extends React.Component{
constructor(props){
super(props);
this.state = {item:{content:'jsx'}}
};
changeItem(e){
this.setState({item:{content:e}});
};
render(){
let {item} = this.state;
return (
<div>
<div> content: {item.content} </div>
<Child callback={value =>this.changeItem({content:value})} />
</div>
)
}
}
class Child extends Component {
render() {
return (
<div>
<button onClick={this.props.callback('jsx')}>item1</button>
<button onClick={this.props.callback('li')}>item2</button>
</div>
)
}
}
Fuction组件
import React,{useState} from 'react'
function Parent(){
const [item,setItem] = useState({content:'jsx'})
return (
<div>
<div> content: {item.content} </div>
<Child callback={value =>setItem({content:value})} />
</div>
)
}
function Child(){
render() {
return (
<div>
<button onClick={this.props.callback('jsx')}>item1</button>
<button onClick={this.props.callback('li')}>item2</button>
</div>
)
}
}
兄弟组件通信
若不借鉴第三方库,原生React是只支持单向数据流的:通过父组件作为桥梁,来让两个组件之间通信(主模块模式)。
类组件
import React from 'react'
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
title:'jsx'
}
}
onChange(title){
this.setState({title})
}
render() {
const {title} = this.state;
return (
<div>
<Child1 title={title} onChange={value =>this.onChange(value)}>
<Child2 title={title} onChange={value =>this.onChange(value}>
</div>
);
}
}
class Child extends Component {
render() {
return (
<div>
{this.props.title}
</div>
)
}
}
Function组件
import React,{useState} from 'react'
function Parent(){
const [title,setTitle] = useState('jsx')
return (
<div>
<Child1 title={title} onChange={value =>setTitle(value)}>
<Child2 title={title} onChange={value =>setTitle(value}>
</div>
)
}
function Child(){
render() {
return (
<div>
{this.props.title}
</div>
)
}
}
爷孙组件通信
1、利用props和callback函数,由父组件传递给子组件,一层层传递下去。
2、利用发布者与订阅者模式(context), 进行共享一些数据。
//context.js
import React from 'react'
export default const LabelContext = React.createContext({content:'jsx li love li'});
export default const LabelProvider = LabelContext.Provider;
export default const LabelConsumer = LabelContext.Consumer;
//App.jsx
import React from 'react';
import { LabelProvider } from './context';
import LabelParent from './pages/label';
export default class App extends Component {
construltor(props){
super(props);
this.state = {context:{content:'jsx li love li'}}
}
render() {
const {context} = this.state;
return (
<LabelProvider value={context}>
<LabelParent />
</LabelProvider>
)
}
}
类组件
//parent.jsx ---consumer
import { LabelConsumer } from '../../context'
export default class LabelParent extends Component {
render() {
return (
<div>
<LabelConsumer>
{labelContext => <div>{labelContext.content}</div>}
</LabelConsumer>
LabelParent
</div>
)
}
}
//parent.jsx ---contextType
import { LabelContext } from '../../context'
export default class LabelParent extends Component {
static contextType = LabelContext
render() {
return (
<div>
{this.context.content}
LabelParent
</div>
)
}
}
Fuction组件
import React ,{useContext} from 'react';
import { LabelContext } from '../../context';
export default function LabelParent (){
const {content} = useContext(LabelContext)
return (
<div>
{content}
Scene
</div>
)
}
任意组件通信
总共有三种办法:共同祖先法、消息中间件、状态管理
共同祖先法
只要找到两个组件的共同祖先,从而转化为通过共同祖先之间通信。
消息中间件
利用观察者模式,将两个组件之间的耦合解耦成了组件与消息中心+消息名称的耦合,但为了解耦却引入全局消息中心和消息名称,消息中心对组件的入侵性很强和第三方组件通信不能使用这种方式。
class EventEimtter{
constructor(){
this.eventMap = {};
}
sub(name,cb){
const eventList = this.eventMap[name] = this.eventMap[name] || [];
eventList.push(cb)
}
pub(name,...data){
(this.eventMap[name] || []).forEach(cb =>cd(...data))
}
}
//全局消息工具
const ec = new EventEimtter();
class ElemComp1 extends Component{
constructor(){
//订阅消息
ec.sub('Label_update',()=>{console.log('jsx')})
}
}
class ElemComp2 extends Component{
constructor(){
//发布消息
ec.pub('Label_update'})
}
}
状态管理(Redux)
状态管理目前有Redux,React-Redux
基本Redux架构
Redux在单向数据流的基础上强调三个基础原理:首先,唯一数据源,即应用的状态数据应该只存在唯一一个Store上。整个应用只保持一个Store,所有组件的数据源就是Store上的状态,每个组件往往只是用树形对象上的一部分数据。然后,保持状态只读。最后,数据改变只能通过纯函数完成。
├── actionCreator
│ └── index.js
├── actionTypes
│ └── index.js
├── index.js
├── reducers
│ └── index.js
├── store
│ └── index.js
└── views
├── ControlPanel.js
├── Counter.js
└── Summary.js
actionTypes 用于定义Action类型,通常暴露一下常量给组件使用,比如Action类型:增加、减少
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
reducers 即处理action的纯函数,通过传入action对象以及旧的state,返回新的state
import * as actionTypes from '../actionTypes'
export default (state,action) =>{
const {cunterCaption} = action
switch(action.type){
case actionTypes.INCREMENT:
return {...state,[cunterCaption]:state[cunterCaption]+1}
case actionTypes.DECREMENT:
return {...state,[cunterCaption]:state[cunterCaption]-1}
default:
return {...state}
}
}
actionCreator 简单返回一个action.js对象
import * as actionTypes from '../actionTypes'
export default {
increment:(caption)=>{
return {
type:actionTypes.INCREMENT,
cunterCaption:caption
}
},
decrement:(caption)=>{
return {
type:actionTypes.DECREMENT,
cunterCaption:caption
}
}
}
store 只是传入初始化的数据和处理Action的纯函数
import { createStore } from 'redux'
import reducer from '../reducers'
const initValues = {
'num':0,
'amout':10,
'profit':2
}
export default createStore(reducer,initValues)
多个reducer的情况
1、reducerA 影响的state只是store上的一个局部状态(index),无法触及到tag,scene
2、dispatch传递action对象过来后,store无法智能选择具体相关的reducer去执行,只会将所有reducer函数全部执行一遍,然后组装成一个完整的store,所以可能触发一些无用功的reducer
3、actionTypes 设置字符串,过于简单话,容易导致原本不相干的reducer处理了action ,因此,最好加前缀来区分
4、store.subscribe 无法智能区分哪些数据发生变化,只会将所有回调统统执行一遍返回新的值\
import { createStore, combineReducers } from 'redux'
import reducerA from './reducers/reducerA'
import reducerB from './reducers/reducerB'
import reducerC from './reducers/reducerC'
const initValues = {
'num':0,
'amout':10,
'profit':2
}
const reducers = combineReducers({
'index':reducerA,
'tag':reducerB,
'scene':reducerC
})
export default createStore(reducers,initValues)
在view层中:
若要用到数据,通过调用store.getState函数 ,获取当前数据
若要修改数据,通过调用store.dispatch 分发action
在组件加载完成后,通过store.subscribe去订阅数据变化的回调函数,一旦数据发生变化,就触发回调同步刷新局部变量
import store from '../store'
import actionCreator from '../actionCreator'
export default class Counter extends Component{
getOwnState(){
return {
value:store.getState()[this.props.caption]
}
}
clickChange = (isIncrement) =>{
const {caption} = this.props
if(isIncrement){
store.dispatch(actionCreator.increment(caption))
}else{
store.dispatch(actionCreator.decrement(caption))
}
}
updateCounter = ()=>{
this.setState({
...this.getOwnState()
})
}
componentDidMount(){
store.subScribe(this.updateCounter)
}
}
整个流程:点击了增加按钮,通过 actionCreator.increment 返回一个 JS 对象,将它传递给 store.dispatch 分发出去,这时候交给 store 的 reducers 纯函数处理,通过 store.getState() 获取当前状态,以及 action 对象,返回一个新的 state,之后再调用 subscribe 的回调函数,将 store 上的变量映射同步更新到局部变量,局部变量通过 setState即可更新视图
改进Redux架构
1,store在每个需要数据的页面都需要引入,显得不够华丽优雅:通过context发布者/订阅者模式 ,将store 挂载到this.contenxt.store上
只是将store 通过从传入的props中转化为context的属性;设置Provider.childContextTypes,否则可能无法生效
//包装Provider组件,用于将store转化到context上
import {Component} from 'react'
import PropTypes from 'prop-types'
class Provider extends Component{
getChildContext(){
return {
store:this.props.store
}
}
render(){
return this.props.children
}
}
Provider.propTypes = {
store:propTypes.object.isRequired
}
Provider.childContextTypes = {
store:PropTypes.object
}
export default Provider
将store从根引入,传递给Provider对象,转化为this.context属性,以后需要用到store数据,只要引用this.context 即可
import React from 'react'
import { render } from 'react-dom'
import Provider from './Provider'
import ControlPanel from './views/ControlPanel'
import store from './store'
render(){
<Provider store={ store } >
<ControlPanel />
</Provider>,
document.getElementById('root')
}
2,由于将全局变量到局部变量的映射的逻辑跟视图组件耦合在一起,不利于维护:需要拆分成纯函数组件和容器组件
纯函数组件:没有react生命周期的事件,通过props传递的值进行渲染
jsx const Counter = (props) => { const { caption, clickChange, value } = props return ( <div> <input type='button' value='-' onClick={ () => clickChange(false) } /> <input type='button' value='+' onClick={ () => clickChange(true) } /> <span> { caption } Count: { value } </span> </div> ) }
容器组件:将所有包含全局变量到局部变量的映射的逻辑,若本身有局部state,将本身的state转化为props传递给 纯函数组件进行渲染
class CounterContainner extends Component{
updateCounter(){
this.setState({this.getOwnState()})
}
componentDidMount(){
this.context.store.subscribe(this.updateCounter)
}
render(){
return (
<Counter
caption={this.props.caption}
clickChange={this.updateCounter}
value={this.state.value}
/>
)
}
}
CounterContainer.contextTypes = {
store: PropTypes.object
}
最终Redux架构
减少重复写类似的容器组件,增加一个connect 高阶组件,用于构建一个通用的容器组件
const map = new WeakMap()
export const connect = (mapStateToProps,mapDispatchToProps) =>{
return (WrappedComponent) =>{
const HOOCCompnent = class extends Componet{
constructor(...args){
super(...args)
this.state = {}
}
onchange = () =>{
this.setState({})
}
componentDidMount(){
this.context.store.subscribe(this.onchange)
}
//shouldComponentUpdate 通过判断当前state跟之前的是否发生变化,决定是否进行vdon diff 进行重新渲染
shouldComponentUpdate(nextProps, nextState) {
return map.get(this).value !== mapStateToProps(this.context.store.getState(), nextProps).value
}
render(){
const store = this.context.store
const stateToProps = mapStateToProps(store.getState(),this.props)
const newProps = {
...this.props,
...stateToProps,
...mapDispatchToProps(store.dispatch,this.props)
}
//shouldComponentUpdate 通过判断当前state跟之前的是否发生变化,决定是否进行vdon diff 进行重新渲染
map.set(this, stateToProps)
return <WrappedComponent {...newProps} />
}
}
HOOCCompnent.contextTypes = {
store:PropTypes.object
}
return HOOCCompnent
}
}
整个流程:WrappedComponent 即传递的进来的纯函数组件,connect 接收两个参数:状态映射以及函数映射。代码的核心在于 store.subscribe 的onChange 函数,通过 setState({})
从而触发组件重新执行 render 函数进行 vdom 的diff, mapStateToProps 函数执行后返回的便是最新全局变量返回的局部映射,然后将新的值通过 props 的方式传递给WrappedComponent 纯函数组件,从而引起重新渲染。
const Counter = ({caption,value,increment,decrement}) =>{
return (
<div>
<input type='button' value='-' onClick={ increment } />
<input type='button' value='+' onClick={ decrement} />
<span> { caption } Count: { value } </span>
</div>
)
}
const mapStateToProps = (state,ownProps) =>{
return {
caption:ownProps.caption,
value:state[ownProps.caption]
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
increment: () => {
dispatch(actionCreator.increment(ownProps.caption))
},
decrement: () => {
dispatch(actionCreator.decrement(ownProps.caption))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
React-Redux
已经将Provider ,connect 实现好了
import {Provider} from 'react-redux'
……
render(){
<Provider store={ store } >
<ControlPanel />
</Provider>,
document.getElementById('root')
}
import React from 'react'
import { connect } from 'react-redux'
export default connect(mapStateToProps,mapDispatchToProps)(Counter)
状态管理(Mobx)
Mobx 将变量进行双向绑定,不要setState
import {observer} from 'mobx-react'
import {observable} from 'mobx'
@observer
class Timer extends Component{
@observable secondsPassed = 0
componetWillMount(){
setInterVal(() =>{
this.secondsPassed++
},1000)
}
render(){
return (
<span>
Seconds:{this.secondsPassed}
</span>
)
}
}
React.render(<Timer />, document.body)
Mobx 架构目录
.
├── index.js
├── store
│ ├── CounterStore
│ │ └── index.js
│ └── index.js
└── views
├── ControlPanel.js
├── Counter.js
└── Summary.js
CounterStore 减少一大堆回调函数来更新视图
import {observable,computed,action} from 'mobx'
class CounterStore {
@observable counters = {
'num':0,
'amout':10,
'profit':2
}
@computed get totalValue(){
let total = 0
for(let key in this.counters) {
if(this.counters.hasOwnProperty(key)) {
total += this.counters[key]
}
}
return total
}
@computed get dataKeys(){
return Object.keys(this.counters)
}
@action changeCounter = (caption,type) =>{
if(type === 'increment') {
this.counters[caption]++
}else {
this.counters[caption]--
}
}
}
const counterStore = new CounterStore()
export default counterStore
export { counterStore }
store 一样从根注入
import React from 'react'
import { render } from 'react-dom'
import ControlPanel from './views/ControlPanel'
import * as stores from './store'
import { Provider } from 'mobx-react'
render(
<Provider { ...stores }>
<ControlPanel />
</Provider>,
document.getElementById('root')
)
当组件需要用到某个store 需要通过inject注入
import React, { Component } from 'react'
import { observer, inject } from "mobx-react";
@inject('counterStore')
@observer
class Counter extends Component {
render() {
const store = this.props.counterStore
const { caption } = this.props
return (
<div>
<input type='button' value='-' onClick={ store.changeCounter.bind(this, caption, 'decrement') } />
<input type='button' value='+' onClick={ store.changeCounter.bind(this, caption, 'increment') } />
<span> { caption } Count: { store.counters[caption] } </span>
</div>
)
}
}
export default Counter
异步状态管理(saga)
redux-thunk
首先介绍一下redux-thunk ,支持function形式的action,将dispatch句柄交由function去处理,在action进行异步调用,等到结果返回再进行dispatch,从而达到异步更新的效果。
export default function thunk({dispatch,getSate}){
return (next) => (action) =>{
if(typeof action === 'function'){
action(dispatch,getState())
}
next(action)
}
}
const addCountAction = (text) =>{
return {
type:ADD,
text
}
}
const getData = (next) =>(dispatch) =>{
new Promise((resolve) =>{
setTimeout(()=>{
resolve()
},1000)
}).then(()=>{
dispatch(addCountAction(text))
})
}
缺点:容易掉入回调地狱;异步存在在各个action,无法统一管理
redux-saga
saga 是一个Redux中间件,可以通过正常的redux action从主应用程序启动,暂停和取消,它能访问完整的redux state ,也能dispatch redux action;其大概流程是 action1 => redux-saga 监听 =>执行相应的Effect方法 =>返回描述对象=>恢复执行副作用 =>action2;其主要特点:使用generator的方式实现,更加符合同步代码的风格,并且统一监听action,当命中action时,执行对应saga任务,且支持各个saga之间的相互调用。
sage核心API
takeEvery : 发起异步任务
import {delay,put} from 'redux-saga'
function* incrementAsync(){
yield delay(1000)
yield put({
type:'increment'
})
}
import takeEvery from 'redux-saga'
function* watchIncrementAsync(){
yield takeEvery('incremntAsync',incrementAsync)
}
tabkEvery是每次发起‘incrementAsync' action的时候都会执行,若想得到最新那个请求的响应,则可以使用takeLatest
import {takeLatest} from 'redux-saga'
function* watchIncrementAsync(){
yield takeLatest('incrementAsync',incrementAsync)
}
redux-saga 中间件提供很多创建effect(副作用)的函数:
take(pattern):监听未来的action,它创建了一个命令对象,告诉middleware 等待一个特定的action,直到一个与pattern匹配的action被发起,才会继续执行下面的js\
put(action):用来发送action的effect,可以简单的认为是redux中的dispatch函数,当put一个action后,reducer中就会计算新的state并返回\
fork(fn,...args):用来调用其他函数的,但fork函数是非阻塞函数,即js执行完 yield fork(fn,...args) 之后,会立即接着执行下一行的代码js
完整Demo
//saga.js
import {delay,put,takeEvery,all} from 'redux-saga/effects'
function* hisaga(){
console.log('hi,jsx')
}
function* addAsync(){
var url = 'https://xxx/api/getAddParams';
var result = yield fetch(url);
yield put({
type: 'addAsync',
params:result
})
}
function* watchAddAsync(){
yield takeEvery('addAsync',addAsync)
}
export default function* rootSaga(){
yield all([
hisaga(),
watchAddAsync()
])
}
//Main.js 入口
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './saga'
import App from './App'
import reducer from './reducer'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer,applyMiddleware(sagaMiddleware))
sagaMiddleware.run(rootsaga)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.body.appendChild(document.createElement('div'))
)
//reducer.js
export default function reducer(state = {count: 10}, action) {
switch (action.type) {
case 'add': {
return {
count: state.count + 1
}
}
default:
return state
}
}
//myComponent
import React from 'react'
import ReactDOM from 'react-dom'
class MyComponent extends React.Component {
const {add,addAsync} = this.props
render() {
return (
<div className="index">
<p>{this.props.count}</p>
<button onClick={add}>加1</button>
<button onClick={addAsync}>1秒后加1</button>
</div>
)
}
}
export default MyComponent
//app.js
import { connect } from 'react-redux'
import MyComponent from './myComponent'
// Map Redux state to component props
function mapStateToProps(state) {
console.log('state', state)
return {
count: state.count
}
}
// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
return {
increment: () => dispatch({
type: 'add'
}),
incrementAsync: () => dispatch({
type: 'addAsync'
})
}
}
// Connected Component
const App = connect(mapStateToProps,mapDispatchToProps)(MyComponent)
export default App