react学习笔记

react学习

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(,document.getElementById('root'));

ReactDOM的render函数可以把App组件挂载到id为root的节点下。
注意一定要引入react包,是一个jsx语法,需要依赖react包去进行编译。

jsx语法

jsx中如果要使用自己创建的组件,组件一定要以大写开头
想要在jsx中访问state中的数据或者写表达式都要用{}

响应式设计思想和事件绑定

  • 所有的数据在state(状态)中定义
  • 想要再jsx中访问state的数据,需要用{}包括
  • 原生绑定事件比如onchange在jsx中要使用驼峰onChange
  • 绑定事件的时候注意用bind改变this指向。

  • 想要改变state中的数据,需要使用setState函数。
  • 遍历
      
      { this.state.list.map((item,index)=>{ return
    • {item}
    • }) }
    • immutable(不可变的) 禁止去修改state中的数据
    handleItemDelete(index) {
        //禁止使用
        this.state.list.splice(index,1);
        //推荐使用
        const list = [...this.state.list];
        list.splice(index, 1);
        this.setState({
        list,
        });
    }

jsx语法细节补充

  • 注释
    • {//单行注释}
    • {/多行注释/}
  • css类型用className替换class
  • 不转义输出
    {this.state.list.map((item, index) => { return (
  • ); })}
  • label和for
    • for会和循环中的for产生歧义,把for换成htmlFor

父子组件通信

  • 父组件通过属性可以传递给子组件数据或者函数
  • 子组件通过props可以获取父组件传递过来的内容,可以通过父组件传的函数调用这个函数传值给父组件。
  • 代码优化点:
    • 可以用结构赋值的方式解构出props中的数据和函数
    • this指向的绑定统一放在constructor中
    • 循环遍历可以封装成一个函数
      {this.getDoToList()}
    getDoToList() { return this.state.list.map((item, index) => { return ( ); }); }
    • 新版本的react改变值setState接收一个函数,该函数返回一个对象(这个对象就是新的state的值)。接收的函数中有一个形参prevState,就是state中的数据。下面是代码优化
    handleInputChange(e) {
      // this.setState({
      //   inputVal: e.target.value,
      // });
      //setState中的函数是异步执行的(为了提升性能)
      const value = e.target.value;
      this.setState(()=>({
        inputVal:value
      }));
    }
    
    handleBtnClick(){
      // const list = [...this.state.list];
      // list.push(this.state.inputVal);
      // this.setState({
      //   list,
      //   inputVal: "",
      // });
    
      //prevState === this.state
      this.setState((prevState)=>({
        list:[...prevState.list,prevState.inputVal],
        inputVal:""
      }))
    }
    
      handleItemDelete(index) {
      // const list = [...this.state.list];
      // list.splice(index, 1);
      this.setState((prevState)=>{
        const list = [...this.state.list];
        list.splice(index,1);
        return {
          list
        }
      });
    }
    

react衍生出的思考

  • 声明式开发
  • 与其他框架并存
  • 组件化
  • 单向数据流
    • 子组件只接收父组件传过来的数据,不能对数据进行修改。
    • 父组件todoList想要删除数据,可以传递方法给子组件todoItem,子组件调用父组件传过来的方法删除,相当于还是父组件在删除数据。
  • 视图层框架
  • 函数式编程
    • 对自动化测试支持比较好,自动化测试只要给函数一个数值,看结果是否符合预期就可以了。

state、props和render之间的关系

  • 当state或者props中的数据改变的时候,自己的render函数会被执行。
  • 当父组件的render函数执行的时候,里面用到的子组件的render函数也会被执行

了解虚拟DOM

  • 方案一:假如没有react的实现思路
    • 1.state数据
    • 2.jsx模板
    • 3.数据+模板=>生成真实DOM来显示。
    • 4.state数据变化
    • 5.数据+模板=>生成真实DOM,替换原来的DOM
    • 缺陷:
      • 第一次生成了完整的DOM片段
      • 第二次生成了完整的DOM片段
      • 第二次的DOM替换第一次的DOM,非常耗费性能。
  • 方案二
    • 1.state数据
    • 2.jsx模板
    • 3.数据+模板=>生成真实DOM来显示。
    • 4.state数据变化
    • 5.数据+模板=>生成真实DOM,并不直接替换原来的DOM
    • 6.新的DOM(DocumentFragument)和原始的DOM作比对,找差异【损耗性能】
    • 7.找出input框发生了变化(例子)
    • 8.只用新DOM中的input元素,替换掉原始DOM的input元素。
    • 缺陷:性能提升并不明显。
  • 方案三
    • 1.state数据
    • 2.jsx模板
    • 3.数据+模板=>生成虚拟DOM(虚拟DOM就是一个js对象,用它来描述真实DOM)
    ["div",{id:"box"},["span",{},"hello world"]]
    
    • 4.用虚拟DOM来生成真实DOM并显示。
    hello world
    • 5.state数据发生变化
    • 6.数据+模板=>生成新的虚拟DOM
    ["div",{id:"box"},["span",{},"bye bye"]]
    
    • 7.比较原始虚拟DOM和新的虚拟DOM中的区别,找到span中的内容有变化。【损耗性能】,损耗较小,和方案二生成真实dom相比损耗小得多。比对js对象和比对真实dom相比也是极大地提升了性能。
    • 8.直接操作DOM,改变span中的内容。

深入了解虚拟DOM

  • jsx => createElement => 虚拟DOM(js对象) => 真实DOM
render(){
  return 
item
} //等价于下面 render(){ return react.createElement("div",{className:"list-item"},"item") }
render(){
  return 
item
} //等价于下面 render(){ return React.createElement("div",{},React.createElement("span",{},"item")); }
  • 虚拟DOM优点
    • 1.性能得到了极大提升
    • 2.使得跨端应用得以实现(虚拟DOM可以转换成真实DOM,也可以转换成原生应用的组件)。比如React Native

react中的diff算法

  • setState是异步的,假如短时间内调用了3次setState,react就会把多次setState合并成为一个setState,就会省去额外的2次生成虚拟DOM以及虚拟DOM的比对。
  • 同层比对算法:有两个虚拟DOM树,先从第一层(最顶层)比对,第一层不相同直接整个替换;第一层相同,对比第二层,一次类推。
  • 如果每个虚拟dom都有一个key值,比对就可以大大提升性能。所以循环的时候要加上key,且要保障key唯一不重复。
//假如说用index作为key值
//旧的dom结构
a 0
b 1
c 2
//删除a节点 新的dom结构
b 0 
c 1
//此时key就乱了 0和1所代表的的结构和旧的dom结构不一样了
//假如直接用内容本身(假设内容不重复)作为key值
//旧的dom结构
a a
b b
c c
//删除a 新的dom结构
b b
c c
//此时b和c代表的dom结构和旧的dom结构是一样的,大大提升了比对的性能。

ref

  • {this.input=input}} />
  • ref等于,里面写一个回调函数,回调函数的形参就是真实的dom节点,把它挂在到this上就可以访问了。
  • setState是一个异步函数,如果想要在操作数据后得到最新的dom,可以再传一个回调函数。
this.setState((prevState)=>{},()=>{
  //第二个回调函数会在更新完真实dom之后被触发。
})

生命周期函数

  • 在某一时刻组件能够自动调用的函数。
  • react的生命周期主要分为四个大的阶段:
  • Initialization
    • Initialization 初始化state和props
  • Mounting
    • componentWillMount => render => componentDidMount
    • componentWillMount 在组件即将被挂载到页面的时候执行
    • render 去做页面的挂载
    • componentDidMount 组件被挂载到页面后执行
    //注意:componentWillMount 和 componentDidMount只会在页面将要被挂载和页面已经挂在的时候执行一次,后续改变state数据,不会再次出发,而render会被出发多次。
    
  • Updation
  • 当props和state数据变化的时候,会触发一系列的生命周期函数
//1.shouldComponentUpdate 组件是否需要被更新,返回一个bool类型的值
//当父组件render执行的时候,子组件的render也会被执行,但有时候子组件并没有变化,不需要执行,所以可以通过shouldComponentUpdate控制
shouldComponentUpdate(nextProps,nextState){
  if(nextProps.content!==this.props.content){
      return true;
  }else{
      return false
  }
}
//2.如果组件需要被更新会依次执行componentWillUpdate=>render=>componentDidUpdate

//3.componentWillReceiveProps是props改变的时候子组件才有可能执行,满足以下条件才会被执行
/*
  1)接收了父组件传过来的数据
  2)父组件的render函数触发了
    a.如果子组件是第一次存在于父组件中,则不会执行
    b.如果子组件不是第一次存在于父组件中,则会执行
*/
  • Unmounting
  • componentWillUnmount 组件即将被从页面移除的时候会被执行

发送ajax请求的时机

  • componentWillMount
    • 只是react的话没有问题,但是写reactNactive和用react写服务器端的重构时会有一些冲突,为了避免这种情况,推荐使用componentDidMount
  • componentDidMount 【推荐】

redux

  • Redux = Reducer + Flux


    image.png
  • redux的设计原则
    • store是唯一的
    • 只有store能改变自己的内容
    • reducer必须是纯函数
      • 纯函数特点:给固定的输入(传参),就会有固定的输出(返回值),而且不会有副作用(不改变函数外部定义的任何值)。
      • 函数中有异步操作,依赖时间的返回值,都不是一个纯函数
  • 流程
    • 组件中使用dispatch传入action告诉store要进行的操作,store通过reducer(是一个函数)完成相应的操作,返回一个新的对象newStore,store再把自身的数据替换成newStore的,组件就可以拿到store中的新数据了。
  • 注意:
    • reducer不能直接改变store中的数据,通过返回一个新对象告诉store最新的结果。
  • store的几个常用api
    • createStore
    • store.dispatch
    • store.getState
    • store.subscribe
  • 比较规范的代码如下
    • 创建actionTypes.js来定义action中的type行为(定义为常量在其他地方引用规避出现拼写错误)
export const CHANGE_INPUT_VAL = "change_input_val";
export const ADD_TODO_ITEM = "add_todo_item";
export const DEL_TODO_ITEM = "del_todo_item";
  • reducer.js 暴露一个函数,该函数接收当前state的值以及action并返回新的store的值
const defaultState = {
    inputVal:"",
    list:["春花秋月何时了","往事知多少"]
}
import { CHANGE_INPUT_VAL,ADD_TODO_ITEM,DEL_TODO_ITEM } from "./actionTypes";
//reducer是一个函数,返回一个新对象;不能直接修改state中的值
export default (state=defaultState,action)=>{
    if(action.type==CHANGE_INPUT_VAL){
        const newState = JSON.parse(JSON.stringify(state));
        newState.inputVal = action.value;
        return newState;
    }else if(action.type==ADD_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.push(state.inputVal);
        newState.inputVal = "";
        return newState;
    }else if(action.type==DEL_TODO_ITEM){
        const newState = JSON.parse(JSON.stringify(state));
        newState.list.splice(action.index,1);
        return newState;
    }
    return state;
}
  • 创建actionCreators.js来定义action,方便其他地方引入
import { ADD_TODO_ITEM,DEL_TODO_ITEM,CHANGE_INPUT_VAL } from "./actionTypes";
export const getAddTodoItemAction = ()=>({
    type:ADD_TODO_ITEM
})
export const getDelTodoItemAction = (index)=>({
    type:DEL_TODO_ITEM,
    index
})
export const getChangeInputValAction = (value)=>({
    type:CHANGE_INPUT_VAL,
    value
})
  • todoList.js 组件业务代码
import { getAddTodoItemAction,getChangeInputValAction,getDelTodoItemAction } from "./store/actionCreators";

class TodoList extends Component {
  constructor(props) {
    super(props);
    this.state = store.getState();
    this.addTodoItem = this.addTodoItem.bind(this);
    this.changeInputVal = this.changeInputVal.bind(this);
    this.handleStoreChange = this.handleStoreChange.bind(this);
  }
  componentDidMount(){
    /*订阅store变化 改变state中的值*/
    store.subscribe(this.handleStoreChange);
  }
  render(){
    return (
      
       (
          
            {item}
          
        )}
      />
    )
  }
  addTodoItem() {
    const action = getAddTodoItemAction();
    store.dispatch(action);
  }
  delTodoItem(index){
    const action = getDelTodoItemAction(index);
    store.dispatch(action);
  }
  changeInputVal(e) {
    const action = getChangeInputValAction(e.target.value);
    store.dispatch(action);
  }
  handleStoreChange(){
    this.setState(store.getState())
  }
}

ui组件和容器组件

  • ui组件负责页面内容渲染(傻瓜组件:专注渲染,不问操作)

  • 容器组件负责控制逻辑(聪明组件:专注控制)

  • 优化代码如下:

class TodoListUI extends Component {
  render() {
    return (
      
( //注意 此处想要调用父函数还传参数,可以在函数中调用父传过来的函数 {this.props.delTodoItem(index)}}> {item} )} />
); } } export default TodoListUI;
  • 当一个ui组件里面没有任何逻辑,只有一个render函数的时候,可以将其改变为一个无状态组件来提高性能。
  • 二次优化如下
const TodoListUI = (props)=>{
  return (
     //此处是jsx代码
    //之前写的this.props就可以换成只写props。
    //普通组件是一个类,生成的对象有生命周期函数,还有render等;无状态组件就是一个函数,用来返回一段jsx,所以性能更快。
  )
}

在redux中使用发送异步请求获取数据

  • 开发工具redux-devtools和中间件redux-thunk结合使用参照相关连接
    • redux-thunk
    • 开发工具redux-devtools
//可以在生命周期componentDidMount中发送异步ajax请求,然后改变store中的数据,最后更新state中的数据
componentDidMount() {
  /*订阅store变化*/
  store.subscribe(this.handleStoreChange);
  axios.get("/data/list.json").then(({data}) => {
    const action = getInitListAction(data)
    store.dispatch(action)
    this.setState(store.getState())
  });
}

redux-thank中间件(redux的中间件)实现ajax数据请求

  • actionCreators.js
export const getInitListAction = (list)=>({
    type:INIT_LIST,
    list
})
//action返回一个函数,配合redux-thunk使用。
export const getTodoList = ()=>{
    return (dispatch)=>{
        axios.get("/data/list.json").then(({data})=>{
            const action = getInitListAction(data);
            dispatch(action)
        })
    }
}
  • TodoList.js
import {getTodoList} from "actionCreators";
componentDidMount(){
  const action = getTodoList();
  //调用store的dispatch方法,dispatch方法会自行判断传进来的action的类型,如果传入的action是一个对象,就会把对象传给store;如果是函数就会执行这个函数(配合中间件redux-thunk才能实现这个效果),函数实参是store的dispatch方法,再用dispatch派发action给store。
  store.dispatch(action);
}

redux中间件是什么

  • redux-thunk中间件(中间指的是action和store的中间)就是对dispatch函数的一个升级封装;之前store.dispatch函数只接收一个action对象,做一些同步修改数据的操作;有了中间件,可以接收一个函数,我们可以在函数中做一些异步的操作。用中间件有助于我们解决异步代码的问题,也便于自动化测试。

redux-saga入门

  • redux-saga也是redux的中间件,一般情况下redux-thunk已经能够满足我们的业务需求,如果复杂度特别高,我们也可以采用redux-saga;redux-saga会把异步请求数据的操作单独写在自定义js文件中来维护。
  • 基础使用
    • 1.在创建store的时候配置redux-saga中间件
    //创建sagaMiddleware中间件对象
    const sagaMiddleware = createSagaMiddleware();
    const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
          // Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
        })
      : compose;
    
    const enhancer = composeEnhancers(
      applyMiddleware(sagaMiddleware)
    );
    //mount it on the store
    const store = createStore(reducer, enhancer);
    //then run the saga
    sagaMiddleware.run(mySaga)
    export default store;
    
    • 2.第一步中用到了自定义的saga文件
      • 注意saga文件中暴露出去的函数和异步请求数据的函数都是generator函数。
    import { put, takeEvery } from "redux-saga/effects";
    import { getInitListAction } from "./actionCreators";
    import { SET_LIST } from "./actionTypes"
    import axios from "axios";
    
    function* fetchTodoList() {
      try {
        const rst = yield axios.get("/data/list.json");
        const action = getInitListAction(rst.data);
        yield put(action);
      } catch (e) {
          //错误的处理
      }
    }
    function* mySaga() {
        //能够捕获到INIT_LIST action的操作,去执行fetchTodoList
      yield takeEvery(SET_LIST, fetchTodoList);
    }
    
    export default mySaga;
    
    • 3.todiList
    import {
      getSetListAction,
    } from "./store/actionCreators";
    componentDidMount() {
      /*订阅store变化*/
      store.subscribe(this.handleStoreChange);
      //使用redux-saga
      const action = getSetListAction()
      store.dispatch(action)
    }
    

react-redux终章

  • 1.局部安装相关包redux和react-redux
  • 2.使用Provider组件给全局的根组件提供store数据
//程序入口文件index.js代码
import {Provider} from "react-redux";
import TodoList from "./TodoList";
ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);
  • 在要使用store中数据的组建中,把当前组件和sore连接起来
//Todolist.js代码
import {Component} from "react";
import {connect} from "react-redux";
class TodoList extends Component{
    constructor(props){
        super(props);
    }
    render(){
        return (
            
  • dell
) } }

☆☆☆☆☆☆☆☆接着上面的内容☆☆☆☆☆☆☆

//把store中的数据映射到props中,返回的对象就是映射到props中的值
const mapStateToProps = (state)=>{
    return {
        inputVal:state.inputVal
    }
}
//把store的dispatch映射到props中,返回的对象就是映射到props中的值
const mapDispatchToProps = (dispatch)=>{
    return {
        changeInputVal(e){
            const action = {
                type:"change_input_val",
                value:e.target.value
            }
            dispatch(action)
        }
    }
}

//让TodoList与store做连接,如何做连接呢,有个映射关系如下:把mapStateToProps和mapDispatchToProps都映射到props中
export default connect(mapStateToProps,mapDispatchToProps)(TodoList);

加入actionTypes和actionCreator,修改后代码如下

import { Component } from "react";
import { connect } from "react-redux";
import { addItem, changeInputVal,delItem } from "./store/actionCreator.js";
class TodoList extends Component {
  constructor(props) {
    super(props);
  }
  render() {
    const { changeInputVal, add, list, del } = this.props;
    return (
      
    { list.map((item,index)=>{ return
  • {del(index)}} key={index}>{item}
  • }) }
); } }

☆☆☆☆☆☆☆☆接着上面的内容☆☆☆☆☆☆☆

//把store中的数据映射到props中,返回的对象就是映射到props中的值
const mapStateToProps = (state) => {
  return {
    inputVal: state.inputVal,
    list: state.list,
  };
};
//把store的dispatch映射到props中,返回的对象就是映射到props中的值
const mapDispatchToProps = (dispatch) => {
  return {
    changeInputVal(e) {
      const action = changeInputVal(e.target.value);
      dispatch(action);
    },
    add() {
      const action = addItem();
      dispatch(action);
    },
    del(index){
        const action = delItem(index);
        dispatch(action);
    }
  };
};

//让TodoList与store做连接,如何做连接有个映射关系就是mapStateToProps
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

由于TodoList中没有业务逻辑,只有一个render函数,所以可以把TodoList写成一个ui组件,改造后代码如下

import { connect } from "react-redux";
import { addItem, changeInputVal,delItem } from "./store/actionCreator.js";

const TodoList = (props)=>{
  const { changeInputVal, add, list, del,inputVal } = props;
  return (
    
    { list.map((item,index)=>{ return
  • {del(index)}} key={index}>{item}
  • }) }
); } //中间代码省略... /** * 让TodoList与store做连接,如何做连接有个映射关系就是mapStateToProps * 在一个组件中一般是返回组件,此处返回的是connect函数执行的结果,上面代码改造之后 * TodoList是一个ui组件,connect函数把ui组件和业务逻辑结合,最后得到的是一个容器组件, * 所以导出的就是一个容器组件 */ export default connect(mapStateToProps, mapDispatchToProps)(TodoList);

你可能感兴趣的:(react学习笔记)