react笔记

1.定义组件

1.1函数式组件

    function MyComponent(){
            console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
            return 

我是用函数定义的组件

} ReactDOM.render(,document.getElementById('test'))

1.2类式组件

  class MyComponent extends React.Component{
            render(){
                //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
                //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
                console.log('render中的this:',this);
                return 

我是用类定义的组件

} } ReactDOM.render(,document.getElementById('test'))

注意

  • 类式组件没有state,
  • 引入组件要使用大写
  • 组件要大写
  • 执行类式组件过程:
    1.React解析组件标签,找到了MyComponent组件。
    2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
    3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
  • 执行函数式组件过程:
    执行了ReactDOM.render(.......之后,发生了什么?
    1.React解析组件标签,找到了MyComponent组件。
    2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。

2.组件三大属性

2.1state

 class Weather extends React.Component{
  state = {isHot:true}
  render(){
    const {isHot} = this.state;
     return 

今天天气很{this.state.isHot?'炎热':'凉爽'}

} demo = ()=>{ const isHot = this.state.isHot; this.setState({isHot:!isHot}) console.log(this); } } ReactDOM.render(,document.getElementById('example'))

解释:给h2绑定一个点击事件,把这个函数放在原型上,当点击h2的时候,就顺着原型链找到这个点击函数,赋值给onClick的回调函数,点击的时候调用这个点击函数。但是这个时候执行函数,函数中的this指向的是undefined。因为这个点击函数是类中定义的函数,在局部是严格模式,是undefined,所以我们要解决这个问题。
我们将这个点击函数的this指向更改为实例对象,再赋值给实例对象,这是时候就在实例中创建了一个‘相同’的点击函数, 我们点击h2时候的那个是原型上的那个点击函数。这个时候就可以在点击函数中通过this来改变state的状态了

2.2props

  class Person extends React.Component{
    render() {
      const {name,age,sex} = this.props
      return (
        
  • 姓名:{name}
  • 年龄:{age+1}
  • 性别:{sex}
) } } const p = {name:'rose',age:'30',sex:'女'} // 因为有babel和React在这里,所以标签里面能用...p展开运算符来复制对象。别的地方不能 使用 ReactDOM.render(,document.getElementById('text3'))

解释:父子组件中通讯:在父组件中通过<子组件 {...对象}/>将这个值传过去传到子组件的props中

限制传值的类型,必传型

  • 引入props-types
  • 在子组件中使用组件.propTypes和组件.defaultProps
  class Person extends React.Component{
    render() {
      const {name,age,sex} = this.props
      return (
        
  • 姓名:{name}
  • 年龄:{age+1}
  • 性别:{sex}
) } // 当你设置了这个之后,当创建组件就去看你的名字是不是字符串,是不是有内容,当然要引入prop_types包 Person.propTypes = { name: PropTypes.string.isRequired,//限制name为字符串,且为必传 age: PropTypes.number, //限制age为数字 sex: PropTypes.string.isRequired,//限制sex为字符串,且为必传 speak: PropTypes.func //限制speak为函数 } // 设置默认值 Person.defaultProps = { age:18, sex:'不男不女' } } const p = {name:'rose',age:'30',sex:'女'} ReactDOM.render(,document.getElementById('text3'))

类式组件使用props

  function Person(props){
    const {name,age,sex} = props;
   return (
        
  • 姓名:{name}
  • 年龄:{age}
  • 性别:{sex}
) } // 这回这里的限制不能写在里面,要写在外面 Person.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, sex: PropTypes.string.isRequired, speak: PropTypes.func } Person.defaultProps = { age:18, sex:'不男不女' } const p = {name:'rose',age:30,sex:'女'} ReactDOM.render(,document.getElementById('text3'))

2.3refs

方法一:使用字符串

 class Mycomponent extends React.Component{
    showData = ()=>{
      // 通过this.refs来获取组件中标签写了ref = XXX的标签
      const {input1} = this.refs;
      console.log('我被点击了');
      alert(input1.value);
    }
    onblurData = ()=>{
      const {input2} = this.refs;
      alert(input2.value);
    }
    render(){
      return (
        // input1包的双引号,说明是字符串,还有其他形式的
        // 这种形式已经过时了,因为效率低
        
      )
    }
  }
  ReactDOM.render(,document.getElementById('text'))

方法二:使用回调函数

 class Mycomponent extends React.Component{
    showData = ()=>{
      // 这里是this(实例 )
      const {input1} = this;
      alert(input1.value);
    }
    onblurData = ()=>{
      const {input2} = this;
      alert(input2.value);
    }
    render(){
      return(
        // 通过回调函数的形式来书写ref
        // 解释:当创建组件的时候调用render发现,有一个回调函数,会主动给你调用,将当前结点传进去
        //       将结点赋值给当前实例(this,箭头函数中的this指向上面有this的那个this就是render的this就是实例)并取名叫input1(.input1)
        //       将这个ref挂在了实例身上了
        // 这个注释属实鸡肋
        
      )
    }
  }
  ReactDOM.render(< Mycomponent/>,document.getElementById('text'))

方法三:使用ref容器

class Mycomponent extends React.Component{
    // 通过React.createRef调用能返回一个ref容器,该容器能存储被ref标识的节点,
    // 该容器一个节点只能用一个
    myref = React.createRef()
    myref2 = React.createRef()
    showData = ()=>{
      console.log(this.myref);
      alert(this.myref.current.value);
    }
    onblurData = ()=>{

      alert(this.myref2.current.value);
    }
    render(){
      return(
    // 1.点击事件Click的C是大写,因为不是像js一样操纵的是原生DOM,而是自定义(合成)事件 为了更高的兼容性
    // 4.React中的事件是通过事件委托的形式来给最外层的元素处理的  为了高效
      
      )
    }
  }
  ReactDOM.render(< Mycomponent/>,document.getElementById('text'))

当我们要在事件函数中传递参数的时候要使用高阶函数 合理化

 class Login extends React.Component{
    state = {
      username:'',
      password:''
    }
  // 所以我们要将这个函数的返回值为一个函数作为onChange的回调函数!!实在要夸一句妙啊
    saveFormData = (datatype)=>{
     return (e) => {
      //  更妙的是这里,使用[]来读对象下的属性,我们一般情况都是使用对象.属性
      // 直接datatype就是将datatype这个属性添加到state中,而不是将username等传进去
      this.setState({[datatype]:e.target.value})
     }
    }
    handleSubmit = (e) =>{
      e.preventDefault();
    }
    render(){
      return (
        // 在这里面,用户名,密码都要调用函数,当还有邮箱,复选框,文本框等,太多了。所以我们onChange调用事件改变一下
        // 在这里我们saveFormData后面不能加小括号,因为加了小括号的意思是:
        //  将调用saveFormData这个函数,username传过去会被e接收,再将返回值交给onChange的回调函数
        // 而我们想要的是将这个saveFormData函数作为onChange的回调函数,当改变的时候就调用
        
) } } ReactDOM.render(,document.getElementById('text'))

解释:
高阶函数:符合下面其中一个就是高阶函数
1.传入的参数是函数
2.返回的是一个参数
我们这里的saveFormData就是一个高阶函数
还有Promise setTimeOut map set filter 等等

函数的柯里化:通过函数调用的方式继续返回函数的方式,实现多次接收参数最后统一处理
比如这里的saveFormData:先将username传进来,调用那个箭头函数,将e传进来,当username和e两个
参数都传进来后同意进行处理。

function sum(a){
 ruturn (b)=>{
   return (c)=>{
     return a+b+c
   }
 }
}
const result = sum(1)(2)(3)
console.log(result)

3.生命周期

我们之前在讲类式组件和函数式组件的时候讲了两个的执行过程

  • 执行类式组件过程:
    1.React解析组件标签,找到了MyComponent组件。
    2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
    3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
    ...

但是当我们new出该实例到呈现到页面上组件经历了许多的过程

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染
    1.constructor()
    2.在更新之前获取快照----getDerivedStateFromProps
    3.render()
    4.组件挂载完毕的钩子----componentDidMount() =====> 常用
    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息

  • 更新阶段: 由组件内部this.setSate()或父组件重新render触发
    1.getDerivedStateFromProps
    2.控制组件更新的“阀门”----shouldComponentUpdate() 返回值为true才进行下去
    3.render()
    4.getSnapshotBeforeUpdate
    5.组件更新完毕的钩子----componentDidUpdate(preProps,preState,snapshotValue) =====> 常用

  • 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    1.组件将要卸载的钩子-----componentWillUnmount() =====> 常用
    一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息

4.diff算法

1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?

        1. 虚拟DOM中key的作用:
                1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

                2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 
                                  随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

                                a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
                                            (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
                                            (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

                                b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
                                            根据数据创建新的真实DOM,随后渲染到到页面
                                
        2. 用index作为key可能会引发的问题:
                            1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
                                            会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

                            2. 如果结构中还包含输入类的DOM:
                                            会产生错误DOM更新 ==> 界面有问题。
                                            
                            3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
                                仅用于渲染列表用于展示,使用index作为key是没有问题的。
                
        3. 开发中如何选择key?:
                            1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
                            2.如果确定只是简单的展示数据,用index也是可以的。

    慢动作回放----使用index索引值作为key

        初始数据:
                {id:1,name:'小张',age:18},
                {id:2,name:'小李',age:19},
        初始的虚拟DOM:
                
  • 小张---18
  • 小李---19
  • 更新后的数据: {id:3,name:'小王',age:20}, {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 更新数据后的虚拟DOM:
  • 小王---20
  • 小张---18
  • 小李---19
  • ----------------------------------------------------------------- 慢动作回放----使用id唯一标识作为key 初始数据: {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 初始的虚拟DOM:
  • 小张---18
  • 小李---19
  • 更新后的数据: {id:3,name:'小王',age:20}, {id:1,name:'小张',age:18}, {id:2,name:'小李',age:19}, 更新数据后的虚拟DOM:
  • 小王---20
  • 小张---18
  • 小李---19
  • 案例

    class Person extends React.Component{
    
        state = {
            persons:[
                {id:1,name:'小张',age:18},
                {id:2,name:'小李',age:19},
            ]
        }
    
        add = ()=>{
            const {persons} = this.state
            const p = {id:persons.length+1,name:'小王',age:20}
            this.setState({persons:[p,...persons]})
        }
    
        render(){
            return (
                

    展示人员信息

    使用index(索引值)作为key

      { this.state.persons.map((personObj,index)=>{ return
    • {personObj.name}---{personObj.age}
    • }) }


    使用id(数据的唯一标识)作为key

      { this.state.persons.map((personObj)=>{ return
    • {personObj.name}---{personObj.age}
    • }) }
    ) } } ReactDOM.render(,document.getElementById('test'))

    总结:最好数据中要有唯一确定的值来确定这个标识

    5.脚手架

    (这里使用的是v5的)

    5.1如何开启脚手架

    • 安装
      npm i -g create-react-app 为创建react项目脚手架库
    • 创建
      create-react-app 项目名
    • 启动
      npm start
      项目的整体技术架构为: react + webpack + es6 + eslint

    5.2react项目内容介绍

    public ---- 静态资源文件夹
        favicon.icon ------ 网站页签图标
        index.html -------- 主页面
        logo192.png ------- logo图
        logo512.png ------- logo图
        manifest.json ----- 应用加壳的配置文件
        robots.txt -------- 爬虫协议文件
     src ---- 源码文件夹
        App.css -------- App组件的样式
        App.js --------- App组件
        App.test.js ---- 用于给App做测试
        index.css ------ 样式
        index.js ------- 入口文件
        logo.svg ------- logo图
        reportWebVitals.js
            --- 页面性能分析文件(需要web-vitals库的支持)
        setupTests.js
            ---- 组件单元测试的文件(需要jest-dom库的支持)
    

    5.3父子组件之间互传数据

    一层层的传
    • 我们一般在src中创建组件components
    • 可以将.js写为.jsx
    • 下载ES7-react插件,使用rcc创建类式组件,rfc创建函数式组件
    • 在v5中一般使用类式组件,因为父子之间能通过props来传数据
    • 状态在哪里,操作状态的方法就在哪里
    • 子传夫数据:在父中定义函数,<子组件 函数={this.函数}/>,然后在子组件中调用函数(函数在this.props中),就能将数据传到父组件
    • 遍历数组使用map((pre,now)=>{})
    • 解构+重命名
      let obj = {a:{b:1}}
      const {a} = obj; //传统解构赋值
      const {a:{b}} = obj; //连续解构赋值
      const {a:{b:value}} = obj; //连续解构赋值+重命名
    • 通过let newList = [dolistObj,...dolist]来将dolistObj追加到dolist的第一行
    关系老远组件之间传

    使用PubSub
    1.下载 npm install pubsub-js --save
    2.引入:import PubSub from 'pubsub-js'
    3.使用:

    在需要数据的组件中
     componentDidMount(){
        // 订阅一个MY TOPIC
            //{isFirst:false,isLoading:true}这个数据对象能被stateObj接收
        var token = PubSub.subscribe('MY TOPIC', (_,stateObj)=>{
          this.setState(stateObj)
        })
      }
      componentWillMount(){
        // 在渲染组件之后取消订阅
        PubSub.unsubscribe(this.token)
      }
    
    
    在传递数据的组件中
    // 发布这个MY TOPIC,传递数据
        PubSub.publish('MY TOPIC',{isFirst:false,isLoading:true});
    原理就是能够发现这个MY TOPIC,并检测
    

    5.4axios

    前提:

    1. React本身只关注于界面, 并不包含发送ajax请求的代码
    2. 前端应用需要通过ajax请求与后台进行交互(json数据)
    3. react应用中需要集成第三方ajax库(或自己封装)
      所以使用axios

    使用:

    • 下载 npm i axios
    • 引入 import axios from 'axios'
    • 使用:
    axios.get('http://localhost:5000/students').then(
                response => {console.log('成功了',response.data);},
                error => {console.log('失败了',error);}
            )
    

    但是我们现在是3000的本地要去请求来自5000的数据,存在跨域问题。解决办法:react脚手架配置代理

    方法一:

    在package.json中追加如下配置

    "proxy":"http://localhost:5000"
    

    说明:

    1. 优点:配置简单,前端请求资源时可以不加任何前缀。
    2. 缺点:不能配置多个代理。
    3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
    方法二
    1. 第一步:创建代理配置文件

      在src下创建配置文件:src/setupProxy.js
      
    2. 编写setupProxy.js配置具体代理规则:

      const proxy = require('http-proxy-middleware')
      
      module.exports = function(app) {
        app.use(
          proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
            target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
            changeOrigin: true, //控制服务器接收到的请求头中host字段的值
            /*
               changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
               changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
               changeOrigin默认值为false,但我们一般将changeOrigin值设为true
            */
            pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
          }),
          proxy('/api2', { 
            target: 'http://localhost:5001',
            changeOrigin: true,
            pathRewrite: {'^/api2': ''}
          })
        )
      }
      

    说明:

    1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
    2. 缺点:配置繁琐,前端请求资源时必须加前缀。

    项目

    export default class Search extends Component {
    
      search = ()=>{
          //获取用户的输入(连续解构赋值+重命名)
          const {keyWordElement:{value:keyWord}} = this
          //发送请求前通知App更新状态
          this.props.updateAppState({isFirst:false,isLoading:true})
    
    
          //发送网络请求
          axios.get(`/api1/search/users?q=${keyWord}`).then(
              response => {
                  //请求成功后通知App更新状态
                  this.props.updateAppState({isLoading:false,users:response.data.items})
              },
              error => {
                  //请求失败后通知App更新状态
                  this.props.updateAppState({isLoading:false,err:error.message})
              }
          )
    
    
      //发送网络请求---使用fetch发送(优化)
          try {
              const response= await fetch(`/api1/search/users?q=${keyWord}`)
              const data = await response.json()
              console.log(data);
              PubSub.publish('atguigu',{isLoading:false,users:data.items})
          } catch (error) {
              console.log('请求出错',error);
              PubSub.publish('atguigu',{isLoading:false,err:error.message})
           }
         }
    }
      render() {
          return (
              

    搜索github用户

    this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/> 
    ) } }

    6.路由

    redux

    • 有啥用?当多个组件要共享一个状态数据的时候,我们将这个数据放在redux中,那个组件需要就去redux中取
    • 原理是啥?


      redux原理图.png

      解释下:就是我需要数据的那个组件调用action的函数,

    • 怎么用?
    • 在src下创建一个redux再在下面创建store reductor action

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