react学习笔记系列(二)(包含redux模式)

react学习笔记系列(二)(包含redux模式)

文章目录

  • react学习笔记系列(二)(包含redux模式)
    • tips
      • 什么是受控组件?
      • 什么是挂载?
      • react生命周期钩子函数
      • react中的DOM操作:
      • props.children实现组件的类似插槽效果:
      • react防范xss攻击:
      • 静态属性:
      • React规范
      • 高阶组件
      • 状态提升
      • context变量
    • react-redux学习
      • 概念扫盲
      • redux模式
        • reducer:
        • createStore(reducer)
        • 关于中间件
      • 原生react-redux原理:react中使用 redux
        • 容器组件container component(src/containers)UI组件presentational component(src/components)
        • react-redux使用规范:
        • connect高阶组件
        • 踩坑点
          • 变与不变
          • store中数据不能持久化

tips

什么是受控组件?

React.js 中的 、、 等元素的 value 值如果是受到 React.js 的控制,那么就是受控组件。

什么是挂载?

React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程称为组件的挂载

react生命周期钩子函数

componentWillMount:组件挂载开始之前,也就是在组件调用 render 方法之前调用。一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行

componentDidMount:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用。(只调用一次哦!)

componentWillUnmount:组件对应的 DOM 元素从页面中删除之前调用。组件销毁前需要一些数据的清理,例如定时器的清理,就会放在 componentWillUnmount 里面去做。
更新阶段的组件生命周期函数:

shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。

componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。

componentWillUpdate():组件开始重新渲染之前调用。

componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

react中的DOM操作:

我们可以给任意代表 HTML 元素标签加上 ref 从而获取到它 DOM 元素然后调用 DOM API。推荐使用将ref属性设置为回调函数而不是ref使用字符串方式:

React之ref详细用法

this.clock=clock}>
//使用时
{this.clock.clientWidth}

props.children实现组件的类似插槽效果:

也就是说:我们的组件实现像html标签一样的嵌套功能–>Card就是一个容器型组件

    class Card
        <div className='card-content'>
          {this.props.children}//这是个数组,此时长度为2,分别为下面的div和p
        div>

       class App
        <Card>
            <div>1div>
            <p>2p>
        Card>

react防范xss攻击:

dangerouslySetHTML 属性传入对象,这个对象的 __html 属性值就相当于元素的 innerHTML,这样我们就可以动态渲染元素的 innerHTML 结构了。

  constructor() {
    super()
    this.state = {
      content: '<h1>React.js 小书h1>'
    }
  }
...  
render () {
    return (
      <div
        className='editor-wrapper'
        dangerouslySetInnerHTML={{__html: this.state.content}} />
    )
  }

静态属性:

PropTypes 规定传入参数的类型;

 //需要引入
import PropTypes from 'prop-types'
 static propTypes = {
    comment: PropTypes.object.isRequired
  }
//isRequired 关键字来强制组件某个参数必须传入:



//不需要引入什么
    static defaultProps = {//没传参数时候的默认参数
        comments: []
    }

React规范

  • static 开头的类属性,如 defaultProps、propTypes。
  • 构造函数,constructor。
  • getter/setter(还不了解的同学可以暂时忽略)。
  • 组件生命周期。
  • _ 开头的私有方法。
  • 事件监听方法,handle*。
  • render开头的方法,有时候 render() 方法里面的内容会分开到不同函数里面进行,这些函数都以 render 开头。
  • render() 方法。

高阶组件

高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过 props 传递数据。

高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。新的组件使用传入的组件作为子组件。

状态提升

当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。

context变量

React.js 的 context 其实像就是组件树上某颗子树的全局变量。

React.js 的 context 就是某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。

使用:

  1. getChildContext 这个方法就是设置 context 的过程,它返回的对象就是 context(也就是上图中处于中间的方块),所有的子组件都可以访问到这个对象。
  2. childContextTypes,它的作用其实 propsType 验证组件 props 参数的作用类似。不过它是验证 getChildContext 返回的对象。为什么要验证 context,因为 context 是一个危险的特性,按照 React.js 团队的想法就是,把危险的事情搞复杂一些,提高使用门槛人们就不会去用了。如果你要给组件设置 context,那么 childContextTypes 是必写的。
  3. 一个组件可以通过 getChildContext 方法返回一个对象,这个对象就是子树的 context,提供 context 的组件必须提供 childContextTypes 作为 context 的声明和验证。

如果一个组件设置了 context,那么它的子组件都可以直接访问到里面的内容,它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过 contextTypes 来声明你想要的 context 里面的哪些状态,然后可以通过 this.context 访问到那些状态。

context 打破了组件和组件之间通过 props 传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context 里面的内容会导致程序的运行不可预料。

但是这种机制对于前端应用状态管理来说是很有帮助的,因为毕竟很多状态都会在组件之间进行共享,context 会给我们带来很大的方便。一些第三方的前端应用状态管理的库(例如 Redux)就是充分地利用了这种机制给我们提供便利的状态管理服务。但我们一般不需要手动写 context,也不要用它,只需要用好这些第三方的应用状态管理库就行了。

//父组件  
static childContextTypes = {
    themeColor: PropTypes.string //这是一个大家都用的状态
  }
  constructor () {
    super()
    this.state = { themeColor: 'red' }
  }

  getChildContext () {
    return { themeColor: this.state.themeColor }
  }
//子组件:
//子组件要获取 context 里面的内容的话,就必须写 contextTypes 来声明和验证你需要获取的状态的类型,它也是必写的,如果你不写就无法获取 context 里面的状态。Title 想获取 themeColor,它是一个字符串,我们就在 contextTypes 里面进行声明。
  static contextTypes = {
    themeColor: PropTypes.string
  }
  render () {
    return (
      <h1 style={{ color: this.context.themeColor }}>this is a test</h1>
    )
  }
}

react-redux学习

概念扫盲

  1. 所有对数据的操作必须通过 dispatch 函数。它接受一个参数 action,这个 action是一个普通的 JavaScript 对象,里面必须包含一个 type 字段来声明你到底想干什么。dispatch 在 swtich 里面会识别这个 type 字段,能够识别出来的操作才会执行对 appState 的修改。
  2. pure function:一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。出现副作用的现象:比如修改了外部传进来的对象,那么它就是不纯的。更简单点理解就是:只要是同样的输入,必定得到同样的输出。纯函数是函数式编程的概念,必须遵守以下一些约束。
  • 不得改写参数

  • 不能调用系统 I/O 的API

  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

redux模式

image

reducer:

初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象)

createStore(reducer)

createStore 接受一个叫 reducer 的函数作为参数,这个函数规定是一个纯函数,它接受两个参数,一个是 state,一个是 action,而正是因为它是一个纯函数所以输入同样的state能渲染一样的视图,这就意味着我们不能在reducer中修改state,因此我们只能做复制做替换,通过Object.assign/…/immutable.js的方式

store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中,返回相应的getState,subscribe,dispatch(相应的功能已经在代码注释中阐明)

关于中间件

middleware是一个可插拔的机制,而中间件的执行原理其实就是对dispatch方法的扩展,比如redux-logger就是在dispatch前后获取旧的和新的state。

关于applyMiddleware源码下面会展示:

// 以下代码都是在这边完全手打的,有出错的地方以后我看到会校对,thx
// 首先需要明确middleware,以redux-logger为例,本质就是操作dispatch
// 和源码不完全一致,但思路一样
function reduxLogger(store){
    // 为什么不直接用store.dispatch越过这一步呢,因为我们在这个地方需要一个新的dispatch,且看applyMiddlewares
    return function (dispatch){
        return function(action){
            console.info(`before update ${store.getState()}`);
            dispatch(action);
            console.info(`before update ${store.getState()}`);
        }
    }
}

// 关于洋葱模型的compose函数
// compose(f1,f2,f3)(args)===>f3(f2(f1(args))),相对源码我稍微做了一点改动,源码是f1(f2(f3(args)))
// https://github.com/reduxjs/redux/blob/v3.7.2/src/compose.js
function compose(...funcs){
    if(funcs.length===0){
        return args=>args;
    }
    if(funcs.length===1){
        return funcs[0];
    }
    return funcs.reduce((res,item)=>args=>item(res(args)));
}
//applyMiddlewares,本质还是对dispatch的扩展
function applyMiddlewares(...middlewares){
    return (createStore)=>{
        return (reducer)=>{
            let store=createStore(reducer);
            // 一个函数数组,可以调用dispatch返回新的dispatch
            let chains=middlewares.map(item=>item(store));
            let newDispatch=compose(...chains)(store.dispatch);
            return {
                ...store,
                dispatch:newDispatch
            }
        }
    }
}

原生react-redux原理:react中使用 redux

react中管理数据的流程是单向的,从派发action到发布订阅渲染视图是一条路走到头。

1.reducer:初始化state和返回一个新对象(reducer是一个纯函数,并不会修改state而是利用es6扩展运算符结合覆盖产生一个新对象)

2.createStore(reducer):store.dispatch会自动触发reducer执行,要实现这个功能就是必须把reducer传入到createStore中

3.store放到context里面,让所有子组件都能访问store(结合6)

4.针对大量重复逻辑和对context依赖性过强,使用connect函数返回高阶组件(connect 函数接受一个组件 WrappedComponent 作为参数,把这个组件包含在一个新的组件 Connect 里面,Connect 会去 context 里面取出 store。)

5.此函数的实现使用函数式编程:接收两个参数:mapStateToProps(告诉connect取哪部分数据)mapDispatchToProps(告诉connect如何触发dispatch)

6.抽离出来的provider:提供store

// Provider组件源码
class Provider extends Component {
// 用来设置context对象,返回的就是子树的context
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}
// 用来校验getChildContext返回的context数据类型
Provider.childContextTypes = {
  store: React.PropTypes.object
}




// Provider的使用
class xx extends React.Component{
    let store=createStore(reducer);
    render(){
        return{
            <Provider store={store}>
                <App />
            </Provider>
        }
    }
}

容器组件container component(src/containers)UI组件presentational component(src/components)

UI组件特点:

只负责 UI 的呈现,不带有任何业务逻辑 没有状态(即不使用this.state这个变量)
所有数据都由参数(this.props)提供
不使用任何 Redux 的 API
容器组件特点:

负责管理数据和业务逻辑,不负责 UI 的呈现
带有内部状态
使用 Redux 的 API

react-redux使用规范:

1.定义 action types

  1. 编写 reducer

  2. 跟这个 reducer 相关的 action creators(action creators 其实就是返回 action 的函数,这样我们 dispatch 的时候只需要传入数据就可以了:)

dispatch({ type: ‘INIT_COMMENTS’, xxx})

dispatch(initComments(xxx))

4.划分好 smart组件和dumb组件

5.在index.js里面引入

import { Provider } from ‘react-redux’
import { createStore } from ‘redux’
const store = createStore(commentsReducer)
6.在需要传参进行一些操作的子组件(一般为修改state,然后dispatch)中引入:
import { connect } from ‘react-redux’
导出的时候是connect返回的带参数的组件(参数和mapDispatchToProps一致和propTypes一致)
ReactDOM.render(


,
document.getElementById(‘root’)
);

connect高阶组件

下面讲一下connect 源码:

references:

React-Redux 源码分析(三)-- connect

    /**
     * 1、从context里获取store
     * 2、在componentWillMount 里通过mapStateToProps获取stateProp的值
     * 3、在componentWillMount 里通过mapDispatchToProps获取dispatchProps的值
     * 4、在componentWillMount 里订阅store的变化
     * 5、将获得的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件
     * 6,当然其中包含shouldComponentUpdate的性能优化,selectorFactory函数等等
     * @param mapStateToProps
     * @param mapDispatchToProps
     * @returns {{new(): wrapConnectComponent, prototype: wrapConnectComponent}}
     * @constructor
     */
    const Connect=(mapStateToProps,mapDispatchToProps)=>{
        return (wrappedComponent)=>class wrapConnectComponent extends React.Component{
            static contextTypes={
                store:PropTypes.object,
            };
            constructor(props){
                super(props);
                this.state={
                    allProps:{},
                }
            };
            componentWillMount(){
                const {store}=this.context;
                this._updateProps();
                store.subscribe(()=>this._updateProps());
            };
            _updateProps(){
                // 收集所有的props,setState
                const {store}=this.context;
                const stateProps=mapStateToProps?mapStateToProps(store.getState,this.props):{};
                const dispatchProps=mapDispatchToProps?mapDispatchToProps(store.getState,this.props):{};
                this.setState({
                    allProps:{
                        ...this.props,
                        ...stateProps,
                        ...dispatchProps,
                    }
                })
            };
            render(){
                const {allProps}=this.state;
                return(<wrappedComponent {...allProps} />)
            }
        }
    };

References:

react小书

+new Date()是什么意思?:返回从1970年1月1日0时0分0秒,到该日期的毫秒数(时间戳)

踩坑点

变与不变
  1. 只要组件的状态( props 或者 state )发生了改变,那么组件就会执行render 函数进行重新渲染。除非你重写 shouldComponentUpdate 周期函数通过返回 false 来阻止这件事的发生;又或者直接让组件直接继承 PureComponent 。

而继承 PureComponent 的原理也很简单,它只不过代替你实现了 shouldComponentUpdate 函数:在函数内对现在和过去的 props / state 进行“浅对比”(shallow comparision,具体内容请移步我的:react学习笔记系列(一)即仅仅是比较对象的引用而不是比较对象每个属性的值),如果发现对象前后没有改变则不执行 render 函数对组件进行重新渲染

其实这样一套相似逻辑在 Redux 中也多次存在,在 redux 中也会对数据进行“浅对比”

import {conenct} from 'react-redux'

function mapStateToProps(state) {
  return {
    todos: state.todos,
    visibleTodos: getVisibleTodos(state),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)
//--------------------------------------
function mapStateToProps(state) {
  return {
    data: {
      todos: state.todos,
      visibleTodos: getVisibleTodos(state),
    }
  }
}

注意上面两片代码的区别:

代码片1中:

代码中组件 App 是被 react-redux 封装的组件, react-redux 会假设 App 是一个 Pure Component ,即对于唯一的 props 和 state 有唯一的渲染结果。 所以 react-redux 首先会对根状态(即上述代码中 mapStateToProps的第一个形参 state )创建索引,进行浅对比,如果对比结果一致则不对组件进行重新渲染,否则继续调用mapStateToProps 函数;同时继续对 mapStateToProps 返回的 props 对象里的每一个属性的值(即上述代码中的 state.todos 值和 getVisibleTodos(state) 值,而不是返回的 props 整个对象)创建索引。和shouldComponentUpdate 类似,只有当浅对比失败,即索引发生更改时才会重新对封装的组件进行渲染:这就解决了之前一个问题:store中数据改变,但是比如:使用todos的组件并没有重新渲染:就是state.todos建立的索引没有变,所以要想重新渲染需要:todo:[…state.todos]

代码片2中:

这是一个陷阱,即使 state.todos 和 getVisibleTodos(state) 同样不再发生变化,但是因为每次 mapStateToProps 返回结果{ data: {…} } 中的 data 都创建新的(字面量)对象,导致浅对比总是失败, App 依然会再次渲染

同样,我在学习笔记(3)-react-redux使用问题中也记录过类似的问题。

至此我们知道了:

我们已经知道了 react-redux 中也会对根状态进行浅对比,如果引用发生了改变,才重新渲染组件。 所以当状态需要发生更改时,务必让相应的 reducer 函数始终返回新的对象!修改原有对象的属性值然后返回不会触发组件的重新渲染!(所以使用扩展运算符/object.assign/immutable.js)

因此我们可以总结一下connect的作用

  1. connect是一个高阶组件,将stateProps,dispatchProps,还有ownProps合并起来,一起传给wrappedComponent,将其作为返回的新组件的子组件进行渲染。
  2. connect其实帮我们做了性能的优化的,当state跟props发生改变时,selector如果变化,最终计算出来的结果会进行一次浅对比(areStatePropsEqual = shallowEqual,)来设置shouldComponentUpdate防止重复渲染
store中数据不能持久化

references:

React通过redux-persist持久化数据存储

vuex-persist插件为Vuex持久化存储而生

vue单页面应用刷新网页后vuex的state数据丢失的解决方案

解决vuex在页面刷新后数据被清除的问题

  • 为什么会丢失?
    关于redux为什么丢失的问题,我并没有搜到答案,但是vuex刷新数据丢失是因为数据是存在内存里面导致刷新必然出现数据的重置,刷新页面,以前申请的内存被释放,重新加载脚本代码,变量重新赋值。

  • 怎么解决丢失问题?

    • 使用webStorage
      • 使用redux-persist :redux-persist会将redux的store中的数据缓存到浏览器的localStorage中。
      • vuex-persist:它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中。
    • 使用时机问题?
      • 如果页面发生重载即将数据存入storage中,主要通过监听:beforeunload事件

你可能感兴趣的:(React,react,学习笔记,redux,react-redux)