React学习笔记一

357 阅读24分钟

一  组件通信

1.1 props

最常见的通信方式,用于在组件之间传递数据,父组件可以通过props将数据传递给自组件;

1.2 实例方法

这是第二父组件向子组件传递数据的方法,非常有用,父组件通过使用ref引用子组件来直接调用子组件实例的方法,demo如下代码所示

export default class Parent extends Component {
  constructor(props) {
    super(props);
  }

  componentDidMount() {
    var x = this.foo.myFunc();
    console.log(x) // hello
  }

  render() {
    return (
      <Child ref={foo => { this.foo = foo }} />
    )
  }
}

class Child extends Component {
  constructor(props) {
    super(props);
  }
  myFunc() {
    return "hello";
  }

  render() {
    return (
      <div>子组件</div>
    )
  }
}

1.3  回调函数

子组件给父组件传递数据一般通过回调函数的形式,子组件通过调用父组件传回来的回调函数,将数据传给父组件;

const Child = ({ onClick }) => {
    <div onClick={() => onClick('stoney')}>Click Me</div>
}

class Parent extends React.Component {
    handleClick = (data) => {
        console.log("Parent received value from child: " + data)
    }
    render() {
        return (
            <Child onClick={this.handleClick} />
        )
    }
}

1.4 事件冒泡

这个利用浏览器中DOM的事件机制,和回调函数类似,可以把数据从子组件发送到父组件,当你需要在父组件中捕获来自子组件的DOM事件时,可以采用此方法;

class Child extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>子组件</div>
    )
  }
}

export default class Parent extends React.Component {
    constructor (props) {
      super(props);
    }
    handleClick = (evt) => {
        console.log(evt.target)
    }
    render() {
        return (
            <div onClick={this.handleClick}>
              <Child />
            </div>
        )
    }
}

1.5 全局变量

使用全局变量不是最佳方法,但是比较使用,可以节省大量时间,需要谨慎使用。使用window.x明确定义,便于维护管理。在一个组件的生命周期或者事件监听器里设置window.x全局变量,然后在其他组件里就可以用window.x来读取该数据;

1.6 观察者模式

这种方式可以适用于所有组件通信,一般做法是在componentDidMount生命周期里面订阅事件,在componentWillUnmount里面移除事件,然后在接收事件时调用setState()方法;以下实例通过nodeJS 的event模块实现发布订阅模式;

// App.js
import React, { Component } from 'react';
import List1 from './list1.js';
import List2 from './list2.js';


export default class App extends Component {
    render() {
        return (
            <div>
                <List1 />
                <List2 />
            </div>
        );
    }
}


// list1.js
import React, { Component } from 'react';
import emitter from './events.js';

class List extends Component {
    constructor(props) {
        super(props);
        this.state = {
            message: 'List1',
        };
    }
    componentDidMount() {
        // 组件装载完成以后声明一个自定义事件
        this.eventEmitter = emitter.addListener('changeMessage', (message) => {
            this.setState({
                message,
            });
        });
    }
    componentWillUnmount() {
        emitter.removeListener(this.eventEmitter);
    }
    render() {
        return (
            <div>
                {this.state.message}
            </div>
        );
    }
}

export default List;

// list2.js
import React, { Component } from 'react';
import emitter from './events.js';

class List2 extends Component {
    handleClick = (message) => {
        emitter.emit('changeMessage', message);
    };
    render() {
        return (
            <div>
                <button onClick={this.handleClick.bind(this, 'List2')}>点击我改变List1组件中显示信息</button>
            </div>
        );
    }
}

export default List2;

1.7 Provider,Consumer,Context

react使用Context实现祖代组件向后代组件跨层级传值。

Context API

React.createContext

创建一个Context对象,当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值;

Context.Provider

Provider接收一个value属性,传递给消费组件,允许消费组件订阅context的变化,一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。当Provider的value值发生变化时,内部所有的消费组件都会重新渲染,Provider及其内部consumer组件都不受制于shouldComponentUpdate函数,因此当consumer组件在其祖先组件退出更新的情况下也能更新;

Class.contextType

挂载在class上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这能让你使用this.context来消费最近Context上的那个值。你可以在任何生命周期中访问到它,包括render函数。(你只能通过该API订阅单一的context)

Context.Consumer

这里,React组件也可以订阅到context变更,这能让你在函数式组件中完成订阅context。这个函数接收当前的context值,返回一个React节点。传递给函数的value值等同于组件树离这个context最近的Provider提供的value值。如果没有对应的Provider,value参数等同于传递给createContext()的defaultValue。

代码实例

import React, { Component, useContext } from 'react';

const MyContext = React.createContext({});
const MyProvider = MyContext.Provider;
const MyConsumer = MyContext.Consumer;

const MyBGContext = React.createContext({});
const MyBGProvider = MyBGContext.Provider;
const MyBGConsumer = MyBGContext.Consumer;

export default class ContextPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: {color: 'red'},
      bgTheme: {background: 'red'},
      count: 0
    };
  }



  getRandomColor = () => {
    return "#"+("00000"+((Math.random()*16777215+0.5)>>0).toString(16)).slice(-6);
  };

  changeTheme = () => {
    // const {color} = this.state.theme;
    this.setState({
      theme: {
        color: this.getRandomColor()
      },
      bgTheme: {background: this.getRandomColor()},
      count: 0
    });
  };

  render () {
    const { theme, bgTheme, count} = this.state;
    return (
      <div style={{border: "1px solid #ddd", margin: '50px', padding: '20px' }}>
        <h3>ContextPage</h3>
        <MyProvider value={theme}>
          <MyConsumer>{ctx => <Child {...ctx} />}</MyConsumer>
          <ClassChild />
          <FunctionChild />
        </MyProvider>
        <MyBGProvider value={bgTheme}>
          <MyBGConsumer>{ctx => <Child {...ctx} />}</MyBGConsumer>
          <FunctionChild />
        </MyBGProvider>
        <button onClick = {() => this.setState({count: count + 1})}>
          change count: {count}
        </button>
        <button onClick = {this.changeTheme}>change theme color</button>
      </div>
    );
  }
}

function Child ({color, background}) {
  return (
    <div style={{border: "1px solid #ddd", margin: '10px', color: color, background: background}}>
      Child-{color || background}
    </div>
  );
}

class ClassChild extends Component {
  static contextType = MyContext;
  render() {
    const {color} = this.context;
    console.log("classChild");
    return (
      <div style={{margin: '10px' }}>
        <button
          style={{color: color}}
          onClick = {() => console.log("log classchild")}>
          ClassChild-{color}
        </button>
      </div>
    );
  }
}

function FunctionChild(props) {
  const context = useContext(MyContext);
  const bgContext = useContext(MyBGContext);
  console.log("FunctionChild");
  const {color} = context;
  const {background} = bgContext;

  return (
    <div style={{border: "1px solid #ddd", margin: '10px', color: color, background: background}}>
      FunctionChild: {color || background}
    </div>
  );
};

注意事项

因为context会使用参考标识符(reference identify)来决定何时进行渲染,这里可能会有一些陷阱,当provider的父组件进行重新渲染时,可能会在consumer组件中触发意外的渲染。(value属性总是被赋值为新的对象)

class App extends Component {
  render() {
    return (
      <Provider value={{something: 'something'}}>
        <Toolbar />
      </Provider>
    );
  }
}

为了防止这种情况,将value状态提升到父节点的state里;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      value: {something: 'something'}
    };
  }
  
  render() {
    return (
      <Provider value={this.state.value}>
        <Toolbar />
      </Provider>
    );
  }
}

不熟练时,最好不要在项目中使用context,context一般给第三方库使用;

1.8 路由传参

params

<Route path='/path/:name' component={Path}/>
<link to="/path/2">xxx</Link>
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.name

state

<Route path='/list ' component={Sort}/>
<Link to={{ pathname : '/list ' , state : { name : 'sunny' }}}> 
this.props.history.push({pathname:"/list ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state 

search

<Route path='/web/search ' component={Search}/>
<link to="web/search?id=12121212">xxx</Link>
this.props.history.push({pathname:`/web/search?id ${row.id}`});
读取参数用: this.props.location.search

1.9 状态管理工具

mobx, redux, dva,

二  无状态组件和纯组件

2.1 无状态组件

又叫函数式组件,本质就是一个常规函数,接收一个参数props,并返回一个reactElement;

import React from 'react';
/*
  函数组件:本质就是一个函数,接收一个参数,参数
  是父级传递过来的props,必须又一个返回值,返回
  值就是该组件要输出的内容
*/

function Child(props) {
  const { name } = props;
  return <h1>Hello {name}</h1>
}

function App() {
  return (
    <div>
      <Child name="stoney" />
    </div>
  );
}

export default App;

不能使用string ref, 使用方式如新:

import React, { createRef } from 'react';

function Child(props) {
  const { name } = props;
  let ref = createRef();
  return <h1 ref={ref} onClick = {() => {
    console.log(ref)
  }}>Hello {name}</h1>
}

function App() {
  return (
    <div>
      <Child name="stoney" />
    </div>
  );
}

export default App;

无状态组件没有状态,方法,生命周期,只负责纯展示;主要通过减少继承Component而来的生命周期函数达到性能优化的效果;因为没有生命周期函数,只要父组件的state更新,都会重新render;有以下特点;

  • 纯函数
  • 输入props,输出jsx
  • 没有this,没有实例,没有生命周期,没有state,不能使用string ref;
  • 不能扩展其他方法
  • 使用函数式组件时,应该尽量减少在函数中声明子组件,否则组件每次更新时,都会重新创建这个函数;

2.2 纯组件

react中纯组件是通过控制shouldComponentUpdate生命周期函数,减少render次数来减少性能损耗的。该组件在shouldComponentUpdate生命周期中通过浅比较( 只比较props和state的内存地址),如果内存地址相同,返回false;其他和Component完全一致;React.PureComponent作用在类组件中,React.memeo作用在函数组件中;如果组件的props和state相同时render内容就一致的情况下就可以使用纯组件;需要结合不可变值使用;

export default class Test extends Component {
    constructor(props) {
        super(props);
        this.state = {
            accountNum: '',
            initPassword: '',
            userName: '',
        };
    }

    onChangeText1 = (text, label) => {
      this.setState({accountNum:text.target.value});
    }

    onChangeText2 = (text, label) => {
      this.setState({initPassword:text.target.value});
    }

    onChangeText3 = (text, label) => {
      this.setState({userName:text.target.value});
    }

    render() {
        return (
            <div style={{backgroundColor:"#faf7f7"}}>
                <InputItem label={'账号:'} holder={'请输入账号'} itemValue={this.state.accountNum} handleChangeText={this.onChangeText1} />
                <InputItem label={'初始密码:'} holder={'请输入初始密码'} itemValue={this.state.initPassword} handleChangeText={this.onChangeText2} />
                <InputItem label={'姓名:'} holder={'请输入姓名'} itemValue={this.state.userName} handleChangeText={this.onChangeText3} />
            </div>
        )
    }
}

class InputItem extends React.PureComponent {
    render() {
        let {label,holder,itemValue,handleChangeText} = this.props;
        console.log('renderInputItem',itemValue);
        return (
            <div>
                <div style={{fontSize:14,color:'#000'}}>{label}</div>
                <input
                    placeholder={holder}
                    value={itemValue}
                    onChange={(text)=>handleChangeText(text,itemValue)}
                />
            </div>
        )
    }
}

上面demo中,如果InputItem不使用纯组件,那么,你修改其中任何一个输入框的时候,其他输入框都会重新render;采用纯组件就只会重新render操作的input;大部分情况下,都可以使用PureComponent组件替换Component;

如果使用PureComponent,state的中的某个值是引用类型,一定要记得更新时,更新引用地址(返回一个新对象)

三  受控组件和非受控组件

3.1  受控组件

受控组件的状态是通过React的状态值state或者props控制

3.2 非受控组件

组件不受React的状态值控制,而是通过dom的特性或者React的ref来控制;

export default class App extends Component {
    constructor (props) {
        super(props)
        this.state = {
            name: '',
            flag: true
        }
        this.nameInputRef = React.createRef() // 创建 ref
    }
    render () {
        return (
            <div>
                {/* 这里使用 defaultValue 而不是value,使用 ref */}
                <input defaultValue={this.state.name} ref={this.nameInputRef} />
                <button onClick={this.alertName.bind(this)}>alert value</button>
            </div>
        )
    }
    alertName () {
        const ele = this.nameInputRef.current // 通过 ref 获取 dom 节点
        alert(ele.value)
    }
}

四  SetState

4.1 不可变值

// 错误的写法
this.setState({
    count: this.state.count + 1
})
// 正确的写法
const count = this.state.count + 1
this.setState({ count })

正确修改数组

// 不能使用 push pop splice 等,这样违反了不可变值,会影响 shouldCompententUpdate(浅比较) 判断
this.setState(() => ({
    list1: this.state.list1.concat(100), // 追加
    list2: [...this.state.list2, 100], // 追加
    list3: this.state.list3.slice(0, 3) // 截取
    list4: this.state.list4.filter(item => item > 100) // 筛选
}))

正确修改对象

this.setState(() => ({
    obj1: Object.assign({}, this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a: 100}
}))

4.1 SetState是同步还是异步

通常情况下,setState为异步更新数据,比如合成事件,钩子函数,生命周期中为异步,在其他地方,例如setInterval,setTimeout,promise,原生事件中为同步

const count = this.state.count + 1
this.setState({
    count: count
}, () => {
    // 这个函数没有默认参数
    console.log(this.state.count) // 打印更新后的值
})
console.log(this.state.count) // 打印更新前的值


setTimeout(() => {
    const count = this.state.count + 1
    this.setState({ count })
    console.log(this.state.count) // 打印更新后的值
})

componentDidMount () {
    document.body.addEventListener('click', () => {
        const count = this.state.count + 1
        this.setState({ count })
        console.log(this.state.count) // 打印更新后的值
    })
}

4.2 setState 参数为对象和函数的区别

参数为对象

会被合并,结果只执行一次,类似于Object.assign()

初始值 this.state.count = 0
this.setState({
    count: this.state.count + 1
})
this.setState({
    count: this.state.count + 1
})
this.setState({
    count: this.state.count + 1
})
输出值 this.state.count = 1

参数为函数

不会被合并,函数无法合并

初始值 this.state.count = 0
this.setState((prevState, props) => {
   return {
       count: prevState.count + 1
   } 
})
this.setState((prevState, props) => {
   return {
       count: prevState.count + 1
   } 
})
this.setState((prevState, props) => {
   return {
       count: prevState.count + 1
   } 
})
输出值 this.state.count = 3

通过对比,如果需要使用当前组件的状态或者属性值来计算下一个状态值,可以采用函数作为setState的参数;

五  Redux  React-Redux

5.1 redux是什么

redux是一个独立专门用于做状态管理的JS库,提供可预测化的状态管理,可以运行在不同的环境(客户端,服务端,原生应用),可以用在react,angular,vue等项目中,但基本于react配合使用;主要用来管理react应用中多个组件共享的状态;

redux就是一个经典的发布订阅器;

5.2 redux 数据流

5.3  什么情况下需要使用redux

  • 总体原则:能不用就不用,如果不用比较吃力才考虑使用
  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

5.4 Redux三大核心

1,单一数据源

整个应用的state统一存储在一个Object tree中,并且这个object tree只存在唯一一个store中;

2,state是只读的

唯一改变state的方法就是触发action, action是一个用于描述已发生事件的普通对象

这样就确保了视图和网络请求都不能直接去修改state,相反,它们只能表达想要修改的意图,因为所有的修改都被集中化处理,并且严格按照一个接一个的顺序执行;

store.dispatch({type: 'COMPLETE_TODO', index: 1})

3,使用纯函数来执行修改

Reducers只是一些纯函数,它只接收先前的state和action,并且返回新的state。可以复用,可以控制顺序,传入附加参数;

5.5 Redux的组成

1,state

state对象:通常我们会把应用中的数据存储到一个对象树(Object tree)中进行统一管理,我们把这个对象树称为state, state是只读的,这里需要注意,为了保证数据状态的可维护和测试,不推荐直接修改state中的原数据; 通过纯函数修改state;

在开发react项目中,大致可以把state分为三类

  • DomainDate:可以理解成为服务器端的数据,比如:获取用户的信息,商品的列表等等;
  • UI State:决定当前UI决定展示的状态,比如:弹框的显示隐藏,受控组件等等
  • App State: App级别的状态,比如:当前是否请求的loading,当前路由信息等可能被多个组件去使用到的状态

什么是纯函数:

1,相同的输入永远返回相同的输出
2,不修改函数的输入值
3,不依赖外部环境状态
4,无任何副作用 

 使用纯函数的好处:
1,便于测试
2,利于重构

2,Action-事件

我们对state的修改是通过reducer纯函数进行的,同时通过传入action来进行具体的操作; 

Action是把数据从应用传到store的载体,它是store数据的唯一来源,一般来说,我们可以通过store.dispatch()将action传递给store;Action的特点:

  • Action本质就是一个javascript的普通对象
  • Action对象内部必须要有一个type属性来表示要执行的动作
  • 多数情况下,这个type会被定义成字符串常量
  • 除了type字段之外,action的结构随便定义,项目中主要用action创建函数
  • 只是描述了有事情要发生,并没有描述如何去更新state;

有两个属性
type属性:表示要进行的动作类型,增删改查。。。
payload属性:操作state的同时传入的数据; 

但是这里需要注意的是,我们不直接去调用reducer函数,而是通过Store对象提供dispatch方法来调用;

3,Reducer

Reducer本质上就是一个函数,用来响应发送过来的actions,经过处理,把state发送给store的;注意:

  • 在Reducer函数中,需要return返回值,这样store才能收到数据
  • 函数会收到两个参数,第一个参数是初始化的state,第二个参数是action;

4,Store

为了对state,reducer,action进行统一管理和维护,我们需要创建一个store对象;Store就是把action与reducer联系到一起的对象,主要职责:

  • 维持应用的state
  • 提供getState()方法获取state
  • 提供dispatch()方法发送action
  • 通过subscribe()来注册监听
  • 通过subscribe()返回值来注销监听

redux API:

createStore(reducer, [preloadedState], enhancer);

 -reducer(Function):接收两个参数,分别是当前的state树和需要处理的action,返回新的state树 ;
-[preloadedState](any): 初始时的state。在同构应用中,你可以决定是否把服务端传过来的state合并(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。 如果你使用combineReducers创建 - reducer,他必须是一个普通对象,与传入的keys保持同样的结构。否则,你可以自由传入任何reducer可理解的内容。 enhancer(Function): Store enhancer是一个组合store creator的高阶函数,返回一个新的强化过的store creator。这里与middleware相似,它也允许你通过复合函数 改变store接口。

返回值(Store): 保存了应用所有state的对象,改变state的唯一方法是dispatch action,你也可以subscribe监听state的变化,然后更新UI; 

 reducer -reducer(state, action) 

 store 

 -getState()
-dispatch(action)
-subscribe(listener)
-replaceReducer(nextReducer)
combineReducers(reducers)
将reducer函数拆分成多个单独的函数,拆分后的每个函数负责独立的state applyMiddleware(...middlewares)中间件

下面代码简单的介绍了redux的用法:

function reducer(state = {
  nub: 1,
  name: 'stoney'
}, action) {
  switch(action.type) {
    case "EDIT":
      return {
        ...state,
        nub: state.nub + 1
      }
  }
  return state;
}

let store = createStore(reducer);
store.subscribe(() => {
  console.log("发生了修改了", store.getState())
})

setInterval(() => {
  store.dispatch({
    type: 'EDIT'
  });
  // 默认情况下 dispatch是一个同步操作
  // console.log(store.getState())
}, 3000);

5.6 React-redux

react-redux就是redux官方出的用于配合react的绑定库,能够使react组件从redux store中方便的读取数据,并且向store中分发actions以此来更新数据;

redux核心源码

export default function createStore(reducer, preloadedState, enhancer) {
  // 实现第二个形参选填
  // 只有当第二个参数传入的是中间件才会执行下面的代码
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  let currentReducer = reducer;
  let currentState = preloadedState; // 整个应用所有的state都存储在这个变量里
  let currentListeners = []; // 订阅传进来的回调函数 <=> Button.addEventListener('click', () => {...})

  // 这是一个很重要的设计
  let nextListeners = currentListeners;

  function getState() {
    return currentState;
  }

  function subscribe(listener) {
    if(nextListeners === currentListeners) {
      // 浅复制
      // 实际上nextListeners就是currentListeners,避免直接操作currentListeners,因为其他地方会调用到currentListeners,从而造成数据不一致。
      nextListeners = [...currentListeners];
    }
    nextListeners.push(listener);

    return function unsubscribe() {
      if(nextListeners === currentListeners) {
        // 浅复制
        nextListeners = [...currentListeners];
      }

      const index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
    }
   }

  // Button.addEventListener('click', () => {...})
  // Button.removeEventListener('click', () => {...})

  function dispatch(action) {
    currentState = currentReducer(currentState, action); // 调用reducer来更新数据
    const listeners = (currentListeners = nextListeners); // 保证当前的listeners是最新的
    for(let i = 0; i < listeners.length; i++) {
      listeners[i](); // 依次执行回调函数
    }
    return action;
  }

  // 手动触发一次dispatch初始化
  dispatch({ type: 'INIT' });

  return {
    getState,
    dispatch,
    subscribe
  }
}

Redux就是帮我们用一个变量存储所有的State,并且提供了发布功能来修改数据,以及订阅功能来触发回调(但是回调之后干嘛,自己解决)

1,react-redux中两个重要的成员

2,Provider

provider包裹在跟组件的最外层,使得所有的组件都可以拿到state

provider接收到store作为props,然后通过context往下传递,这样react中任何组件都可以通过context获取到store;

provider核心源码

import React from 'react';
import PropTypes from 'prop-type';

export default class Provider extends React.Component {
  // context 往所有子组件,孙子组件里传递数据
  // props 父组件往子组件里传递数据
  // state 组件自身的数据

  // 声明一个context数据
  getChildContext() {
    return { store: this.store }
  }

  constructor(props, context) {
    super(props, context)
    this.store = props.store;
  }

  render() {
    return React.Children.only(this.props.children)
  }
}

Provider.childContextTypes = {
  store: PropTypes.object
}

Provider就是通过React的Context API把数据往下传;

3,connect

  • Provider内部组件如果想要使用到state中的数据,就必须要connect进行一层包裹封装;(connect本质是高阶组建)
  • connect就是方便我们组件能够获取到store中的state;

connect核心源码:

import React from 'react';
import PropTypes fro  'prop-types';

// HOC
const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => (WrapComponent) => {
  return class ConnectComponent extends React.Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor(props, context) {
      super(props, context);
      this.state = {
        props: {} // 声明了一个叫props的state
      }
    }

    componentDidMount() {
      const { store } = this.context // 从Context 中拿到store对象
      store.subscribe(() => this.update()) // 订阅Redux的数据更新
      this.update();
    }

    // 每次数据有更新的时候,就会调用这个放法
    updated() {
      const { store } = this.context; // 从Context 中拿到store对象
      const stateProps = mapStateToProps(store.getState()) // 把stores中的全部数据传递到组件内部
      const dispatchProps = mapDispatchToProps(store.dispatch) // 把store。dispatch传到组件内部

      // 调用setState 触发组件更新
      // 将最新的state以及dispatch合并到当前组件的props上
      this.setState({
        props: {
          ...this.state.props,
          ...stateProps,
          ...dispatchProps
        }
      })
    }

    render () {
      // 传入props
      return <WrapComponent {...this.state.props}></WrapComponent>
    }
  }
}

export default connect;

connect就是一个高阶组件,接收Provider传递过来的store对象,并订阅store中的数据,如果store中的数据发生改变,就调用setState触发组件更新;

4,redux是如何将state注入到react组件上去的

  1. 与react产生关联的是React-Redux库
  2. Redux的原理就是一个发布订阅器,帮我们用一个变量存储所有的state,并且提供了发布功能来修改数据,以及订阅功能来触发回调;
  3. 而React-Redux的作用就是订阅Store里的数据的更新,它包含两个重要元素,Provider和connect方法;
  4. Provider的作用就是通过Context API把store对象注入到React组件上去
  5. 而connect方法就是一个高阶组件,在高阶组建里通过订阅store中数据的更新,从而通过调用setState方法来触发组件更新;

下面demo介绍了redux, react-redux的用法,实现了一个加数的功能;

index.js代码:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './redux/App';
import store from "./redux/reducer/index"
import {Provider} from 'react-redux';

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

App.js代码:

import React from 'react';
import Inner from './inner';

function App() {
  return (
    <div>
      <Inner name="stoney" />
    </div>
  )
}

export default App;

inner.js代码:

import React from 'react';
import {connect} from 'react-redux';

function Inner(props) {
  // console.log(props);
  let {nub, dispatch} = props;
  return (
    <div>
      <p>nub: <strong>{nub}</strong></p>
      <button
        onClick={() => {
          dispatch({
            type: 'ADD'
          })
        }}
      >加1</button>
    </div>
  );
}

// console.log(connect)
export default connect((state, props) => {
  // console.log(state, props);
  return {
    ...props,
    nub: state.nub
  };
})(Inner);

/**
 * connect((store.state, props) => {
 * // 允许对props和store进行一个合并处理
 *  return {
 *     传递给组件的数据
 *  };
 * })(INner);
 */

 /**
  * connect(function(store中的state, 父组件传入的props){
  *   return 要传递给组件的信息
  * })(要包装的组件)
  */

reducer/index.js代码:

import {createStore} from 'redux';

function reducer(state = {
  nub: 1,
  nub2: 2
}, action) {
  switch(action.type) {

    case 'ADD':
      return {
        ...state,
        nub: state.nub + 1
      }
  }
  return state;
}

const store = createStore(reducer);

export default store;

六 Mobx

6.1 Mobx的核心概念

Mobx背后的哲学是:

任何源自应用状态的东西都应该自动地获得;即当状态改变时,所有应用到的状态都会自动更新;

核心概念

  • Observable state(可观察的状态):驱动应用的数据
  • computed values:计算值,定义在相关数据发生变化时自动更新的值;如果你想创建一个基于当前状态的值时,可以使用computed;
  • Reactions:反应,当状态改变时自动发生,但它不是产生一个新的值,而是会产生一些副作用,比如打印到控制台,网络请求,递增的更新React组件树以修补DOM等;
  • AutoRun:Mobx中的数据依赖基于观察者模式;

6.2 Redux和Mobx对比

  • Redux的编程范式是函数式编程,Mobx是面向对象编程;redux比较复杂,函数式编程思想相对较难,同时需要借助一系列的中间件来处理异步操作和副作用;
  • Redux的理想数据是immutable的,意味着状态是只读的,不能直接去修改它,每次都返回一个新的数据,同时使用纯函数;而Mobx数据都是一份引用;状态可变,因此redux支持数据回溯;
  • 使用Mobx组件可以做到精确更新,主要因为Mobx的observable;Redux是用dispatch进行广播,通过provider和connect来对比前后差异更新,有时需要自己些ShouldComponentUpdated函数;
  • Redux全局只有一个store,mobx往往是多个store;
  • 大型项目中,可以使用redux作为全局项目管理,mobx作为组件局部状态管理器来通信;

七 react生命周期

7.1 生命周期概述

7.1.1 React 16.3之前

挂载阶段

constructor
componentwillMount
render
componentDidMount

更新阶段

父组件更新引起的组件更新

componentWillReceiveProps(nextProps);
shouldComponentUpdate(nextProps, nextState);
componentWillUpdate(nextProps, nextState);
render();
componentDidUpdate(prevProps, prevState);

组件自身更新

shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate

卸载阶段

componentWillUnmount

7.1.2 现在

**挂载阶段
**

constructor
static getDerivedStateFromProps(props, state) -- 注意this指向
render
componentDidMount

代码演示

import React, {
  Component
} from "react";
class Child extends Component {
  constructor(props) {
    super(props);
    this.state = {
      age: 8
    }
    console.log(1, "初始化", "挂载阶段");
  }
  // 利用getDerivedStateFromProps替代了ComponentwillMount和componentWillReceiveProps
  // 拿到了props或者props做了更新,在这里如果state和props有关联,可以在这里进行关联
  static getDerivedStateFromProps(props, state) {
    console.log(2, '组件即将挂载', '挂载阶段或即将更新');
    return true;
  }
  // componentWillMount() {
  //   console.log(2, "即将挂载", "挂载阶段");
  // }
  componentDidMount() {
    console.log(4, "已经挂载入DOM");
  }
  render() {
    console.log(3, "即将把内容渲染进DOM");
    let {name, setName} = this.props;
    let {age} = this.state;
    return (<div id="wrap">
      <p>姓名:<input
        type="text"
        placeholder="请输入新名字"
        value={name}
        onChange={(e) => {
          setName(e.target.value)
        }}
      /></p>
      <p>{name}</p>
      <p>年龄:<input
        type="text"
        placeholder="请输入年龄"
        value={age}
        onChange={(e) => {
          this.setState({age: e.target.value})
        }}
      /></p>
    </div>)
  }
}

class App extends Component {
  state = {
    name: ''
  }
  setName = (name) => {
    this.setState({
      name
    });
  }
  render() {
    let { name } = this.state;
    return <div>
      <Child name={name} setName={this.setName} />
    </div>
  }
}

export default App;

打印结果

1 "初始化" "挂载阶段"
2 "组件即将挂载" "挂载阶段或即将更新"
3 "即将把内容渲染进DOM"
4 "已经挂载入DOM"

更新阶段

父组件引起的组件更新

static getDerivedStateFromProps(props, state)
shouldComponentUpdate()
//componentWillUpdate();
render()
getSnapshotBeforeUpdate();
componentDidUpdate()

组件自身更新

shouldComponentUpdate()
//componentWillUpdate()
render();
getSnapshotBeforeUpdate();
componentDidUpdate()

import React, {Component} from "react";
class Child extends Component {
  state = {
    age: 8
  }
  static getDerivedStateFromProps(props, state) {
    console.log(1, '组件即将挂载', '挂载阶段或即将更新');
    return true;
  }
  shouldComponentUpdate() {
    console.log(2, "即将挂载", "自身状态发生变化");
    return true;
  }
  componentWillUpdate() {
    console.log(3, "即将挂载", "即将更新");
  }
  //组件更新后,即将重新渲染DOM
  getSnapshotBeforeUpdate() {
    console.log(5, "组件更新完毕即将重新渲染DOM")
    let wrap = document.querySelector("#wrap");
    return wrap.innerHTML;
  }
  componentDidUpdate(prevProps, prevState, prevInfo) {
    console.log(6, "组件更新完成");
    console.log(prevInfo);
  }
  render() {
    console.log(4, "即将把内容渲染进DOM");
    let {name, setName} = this.props;
    let {age} = this.state;
    return (<div id="wrap">
      <p>姓名:<input
        type="text"
        placeholder="请输入新名字"
        value={name}
        onChange={(e) => {
          setName(e.target.value)
        }}
      /></p>
      <p>{name}</p>
      <p>年龄:<input
        type="text"
        placeholder="请输入年龄"
        value={age}
        onChange={(e) => {
          this.setState({age: e.target.value})
        }}
      /></p>
    </div>)
  }
}

class App extends Component {
  state = {
    name: ''
  }
  setName = (name) => {
    this.setState({
      name
    });
  }
  render() {
    let { name } = this.state;
    return <div>
      <Child name={name} setName={this.setName} />
    </div>
  }
}

export default App;

卸载阶段

componentWillUnmount

错误处理

static getDerivedStateFromError();

该方法用来获取子组件抛出的错误,返回的是一个对象,该对象被存储在state中,在后续的render方法中就可以根据这个对象的值来进行处理,如:显示不同的UI

componentDidCatch(error, info);

只能捕获生命周期里的错误,不能捕获事件中的错误,且捕获不了异步错误;主要是父组件捕获子组件的错误;

代码演示

import React, {Component} from "react";
class Child extends Component {
  state = {
    age: 8
  }
  componentDidMount() {
    let box = document.querySelector("#box");
    console.log(box.innerHTML);
  }
  render() {
    let {name, setName} = this.props;
    let {age} = this.state;
    return (<div id="wrap">
      <p>姓名:<input
        type="text"
        placeholder="请输入新名字"
        value={name}
        onChange={(e) => {
          setName(e.target.value)
        }}
      /></p>
      <p>{name}</p>
      <p>年龄:<input
        type="text"
        placeholder="请输入年龄"
        value={age}
        onChange={(e) => {
          this.setState({age: e.target.value})
        }}
      /></p>
    </div>)
  }
}

class App extends Component {
  state = {
    name: 'stoney',
    err: false
  }
  static getDerivedStateFromError(err) {
    return {
      err: true
    }
  }
  componentDidCatch(err) {
    console.log(err);
    if(err) {
      this.setState({err: true})
    }
  }
  setName = (name) => {
    this.setState({
      name
    });
  }
  render() {
    let { name, err } = this.state;
    return <div>
      {err ? "子组件出错了" : <Child name={name} setName={this.setName} />}
    </div>
  }
}

export default App;

7.1.3 将来

useEffect(); 副作用(用来代替生命周期)

7.2 挂载卸载过程

1,constructor()

constructor()中完成了React数据的初始化,它接受两个参数:props和context,使用super() 传入这两个参数;(只要使用了constructor(),就必须写super(),否则会导致this指向错误)

2,componentWillMount()

componentWillMount()一般用的比较少,更多的用在服务端渲染时使用,代表组件已经初始化,但是DOM未渲染;

3,render();

根据组件的props和state,return一个React元素,不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面DOM。

4,componentDidMounnt()

组件第一次渲染完成,dom节点已经生成,可以在这个生命周期里调用ajax请求数据,返回数据setState后,组件会重新渲染;

5,componentWillUnmount()

这个生命周期里面完成组件的卸载和销毁,需要注意以下几点:

  • 取消所有的定时器
  • 移除所有的事件监听

7.3 更新过程

1,componentWillReceiveProps(nextProps);

在接受父组件改变后的props需要重新渲染组件时用到的比较多;通过对比nextProps和this.props,将nextProps的state为当前组件的state,重新渲染组件;

2,ShouldComponentUpdate(nextProps, nextState);

唯一用于可以控制组件重新渲染的生命周期,主要用于性能优化;在这里通过添加部分比较逻辑,return false可以组织组件更新;因为react父组件重新渲染会导致所有的子组件都会重新渲染,因此可以在需要的子组件的该生命周期中做判断;

3,componentWillUpdate(nextProps, nextState);

组件进入重新渲染的流程,这里可以拿到nextProps和nextState;

4,componentDidUpdate(prevProps, prevState);

组件更新完毕后,react只会在第一次初始化后进入ComponentDidMount,之后每次重新渲染都会进入这个生命周期,在这里可以拿到更新前的props和state;

5,render()

render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,每次组件更新时,react会根据diff算法比较更新前后的新旧dom树,找到最小的有差异的dom节点,重新渲染;

7.4 新增的生命周期

1,getDerivedStateFromProps(nextProps, prevState);

代替componentWillReceiveProps(),旧版本的componentWillReceiveProps方法判断前后两个props是否相同,如果不同,则将新的props更新到相应的state中。这样会破坏state数据单一数据源,导致组件状态不可预测,也会增加组件的重绘次数;

// before
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({ 
      isLogin: nextProps.isLogin,   
    });
  }
  if (nextProps.isLogin) {
    this.handleClose();
  }
}

// after
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.isLogin !== prevState.isLogin) {
    return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}

componentDidUpdate(prevProps, prevState) {
  if (!prevState.isLogin && this.props.isLogin) {
    this.handleClose();
  }
}

在componentWillReceiveProps中,一般会做两件事情,一是根据props来更新state,二是触发一些回调,如动画或页面跳转等;

在老版本中,这两件事都需要在componentWillReceiveProps中去做;新版本中,官方将更新state与触发回调重新分配到了getDerivedFromProps与componentDidUpdate中,使得组件整体的更新逻辑更为清晰。在getDerivedFromProps中还禁止了组件去访问this.props,强制让用户去比较nextProps与prevState中的值,以确保当前用户用到getDerivedFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去做其他一些让组件自身状态变得不可预测的事情;

2,getSnapshotBeforeUpdate(prevProps, prevState);

代替componentWillUpdate。常见的componentWillUpdate主要用在组件更新前,读取某个DOM元素状态,并在componentDidUpdate中进行相应的处理。两者区别在于:

在react开启异步渲染模式后,在render阶段读取到的dom元素状态并不总是和commit阶段相同,这就导致在componentDidUpdate中使用componentWillUpdate中读取到的dom元素状态是不安全的,因为这个时候的值可能已经失效了;

getSnapshotBeforeUpdate会在最终的render之前被调用,也就是说在getSnapshotBeforeUpdate中读取到的dom元素状态是可以保证与componentDidUpdate中一致的。此生命周期返回的任何值都将作为参数传递给componentDidUpdate;

八  react中的事件

8.1 原生事件和react事件的区别

react中的事件是绑定到document上面的,原生事件是绑定到dom上面,因此相对绑定的地方来说,dom上的事件要优先于document上的事件执行

8.2 react总体设计

react在事件处理上具有如下优点:

  1. 几乎所有的事件都代理(delegate)到document,达到性能优化的目的
  2. 对于每种类型的事件,统一使用拥分发函数(dispatchEven)分发事件
  3. 事件对象(event)是合成对象(syntheticEvent),不是原生的

8.3 原生事件和混合事件响应顺序

class Demo extends Domponent {
    componentDidMount () {
        const parentDom = ReactDom.findDOMNode(this)
        const childDom = parentDom.queneSelector('.button');
        childDom.addEventListen('click',this.onDomClick, false)
    }
    
    onDOMClick = (e) => {
        console.log('dom event!')
    }
    onReactClick = (e) => {
        console.log('react event!')
    }   
    render () {
        return <div onClick={this.onReactClick}>demo</div>
    }
}

// dom event! react event!

如果在onDOMClick上调用了冒泡:

onDOMClick = (e) => {
        console.log('dom event!')
        e.stopPropagation()
    }

由于阻止了冒泡,则合成事件就不会被执行,因此只会输出dom event!;

class Demo extends Domponent {
    componentDidMount () {
        const parentDom = ReactDom.findDOMNode(this)
        const childDom = parentDom.queneSelector('.button');
        childDom.addEventListener('click',this.onDomClick, false)
        childDom.addEventListener('click', this.onDomClick1, false);
    }
    
    onDOMClick = (e) => {
        console.log('dom event!')
    }
    onDomClick1 = (e) => {
        consle.log('dom1 event!')
    }
    onReactClick = (e) => {
        console.log('react event!')
    }  
    onChildClick = (e) => {
        console.log('react1 event!')
        e.stopPropagtion()
    }
    render () {
        return <div onClick={this.onReactClick}>
            <div onClick={this.onChildClick}>demo</div>
        </div>
    }
}

输出结果:

dom1 event! dom event! react1 event!

用原生的解释可以说是调用了阻止冒泡的函数之后,在该函数后面要执行的函数就不会被冒泡到,也就不会执行,但是在react中,是在调用了stopPropagtion之后,就会给自己添加一个标识isPropagtionStopped来确定后面的监听器是否执行

结论:

  1. 合成事件统一注册在document上面,所以原生事件总是比合成事件先执行
  2. 阻止原生事件的冒泡后,会阻止合成事件的执行