React学习笔记(1)

893 阅读12分钟

1. 创建react项目

  • 安装 npx create-react-app my_app
  • 运行 npm start

2. 环境介绍

  • node_modules 依赖包
  • public 入口文件
  • src 源代码文件
  • package.json 配置文件

这些和uniapp或者vue的结构都很像的

3. React和Vue文件结构

  • React和Vue的文件结构是不一样的
  • Vue: 结构是( 模板 —— 逻辑 —— 样式 )
<template>
	<view>
		模板
	</view>
</template>
<script>
	export default {
		data() {
			return {info:"逻辑"}
		}
	}
</script>
<style lang="scss" scoped>
	view{
		样式
    }
</style>
  • React:JSX文件中是逻辑和模板,React需要另外引入样式
import React, { Component } from 'react';
import "./myStyle.css";
export default class test extends Component {
    render() {
        return (
            <div>
            </div>
        );
    }
}

4. React —— JSX语法

JSX 语法就是 JS 加上 XML,解读方式也很简单,遇到 < > 就看成 HTML ,遇到 { } 就按照JS

  • 在遍历渲染的时候尤其明细jsx的语法
 <ul>
      {/* jsx中是遇到花括号就识别为js语法, 遇到尖括号就识别为html语法,
          在遍历完成后返回一个标签,就会解析为html来渲染*/}
      {
          this.props.myNav.map((e,i)=>{
          return <li key={i}>{e}</li>
          })
      }
  </ul>

jsx解析的时候是按标识符来解析代码的

  • jsx语法中是没办法用html的注释的,要用注释只能是 {/* */}花括号把它套起来,jsx解析到花括号就会自动解析成js语法

5. React —— 组件

super小知识

React的constructor里面的super()函数,是用来初始化this的,可以绑定事件到this上。如果用了constructor但是没有写super()函数,用到this的地方都会报错,如果没有写react会默认添加一个空的constructor里面会带上super()

react的组件名不能小写开头,首字母需要大写,其他的随意。否则会警告,且不显示

Warning: The tag <setStateDome> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

组件文件的后缀可以是JS结尾,亦可以JSX结尾,在某些ideaJSX也会提供更多的语法提示,一个React项目是很多组件组成的,这一点在现在模块化开发的前端中,随处可见不管是vue还是啥都是把组件抽离出来,这样代码复用性也比较强。

  • 组件的引入方式也和vue那些差不多,使用ES6的import引入,并且引入的组件也是以标签的形式展现。
import Nav from './Nav/Nav';

//用类的形式创建组件,还有一种是hook形式
export default class App extends React.Component{
    //render渲染函数
    render(){
        const nav1 = ['首页','学习','导航']
        const nav2 = ['吃饭','睡觉','大泡泡']
        const { count } = this.state //这里使用解构赋值,需要什么数据,引入什么数据即可
        return ( 
            <>
            	<div>欢迎使用组件</div>
                    /*通过定义属性得方式,把数据传输给子组件,
                    因为是再函数体内,直接输入变量命即可,
                    自定义属性是可以传输任何类型得数据得*/
                <Nav myNav={nav1} title="这是一个字符串" />
                <Nav myNav={nav2} /> */}
                <State />
            </>
        )
    }
    
} 

小技巧在渲染DOM的时候可以使用解构赋值,这样就可以少了很多代码量

6. React中使用组件局部样式

  • react不像vue在样式加上scoped就可以让样式不污染全局
  • react的解决方法是,引入一个 *.module.css 后缀的文件
    • 之前类选择器是.main, 使用时只需要这样写className="header"。但是在组件局部样式里面,需要在要指定样式的元素上写className={styles.header}。
// js
import React, { Component } from 'react';
import "./myStyle.css";				//之前直接导入的方式
import myStyle from "./myStyle.module.css";    //命名的方式 引用样式就不会污染到环境
export default class componentName extends Component {
    render() {
        return (
            <>
                <div className={myStyle.main}></div>
            </>   
        )
    }
}
// ./myStyle.module.css
.main{
    background-color: pink;
    width: 150px;
    height: 150px;
}

第一个是直接引入的样式,第二个是利用局部组件的方式导入的样式。 组件导入的会编译成独立样式名不会被覆盖。全局样式是会被覆盖的

7. 组件通讯方式

7.1 ,props 是父组件与子组件交互的唯一方式
  • props的特性是不能修改的,只能接收、传输数据!

TypeError:无法添加属性标题,对象不可扩展

## ./Nav/Nav.jsx
export default class Nav extends React.Component{
    render(){
        console.log(this.props.title)
        //接收数据得时候需要在props中获取
        return (
            <div>
                <h3>{this.props.title}</h3>
                <ul>
                    {/*
                        jsx中是遇到花括号就识别为js语法,
                        遇到尖括号就识别为html语法,在遍历完成后
                        返回一个标签,就会解析为html来渲染
                    */}
                    {
                        this.props.myNav.map((e,i)=>{
                        return <li key={i}>{e}</li>
                        })
                    }
                </ul>
            </div>
        )
    }
}
7.2 父组件向子组件传值
  • 在使用子组件的标签中,直接加入使用一个自定义属性就可以传递数据给子组件,子组件中直接 this.props.myData 用props可以直接获取传输过来的数据
// 父组件
 <myChild myData={"父组件传递的数据"}/>
// 子组件
 render() {
      console.log(this.props.myData)
  }
  • 子组件向父组件传递数据,通过子组件调用父组件的方式,并且可以将数据当参数返回
// 父组件
 render() {
      return (
          <div className="main">
               {/* <Login/> */}
              <myChild callback={this.childValue}/>
          </div>
      )
  }
  childValue = (data) =>{
      console.log('app',data)
      this.setState({
          dataList:data
      })
  }
 
// 子组件
  handleClick(){
      this.props.callback(this.state.count)
  }
7.3 表单控件
  • react使用控件的时候需要添加上 onChange 绑定个方法
constructor(){
    super();
    this.state={
        value:''
    }
}
onChangeValue=(e)=>{
    this.setState({
        value:e.target.value
    })
}
render(){
	return <input type="text" value={this.state.value} onChange={this.onChangeValue}/>
}
  • 非控制组件 Refs 可以操作DOM上的节点或者render方法中创建的元素节点,
  • ref适用于这几种情况
    • 管理焦点,文本选择或媒体播放
    • 触发强制动画
    • 集成第三方DOM库
  • refs需要使用React.createRefs()创建,并通过ref属性附加到React元素中,勿过度使用Refs。 特殊情况才去操作DOM

ref可以获取绑定的DOM节点,并且操作该节点,像获取数据或者修改样式等等。。

constructor(){
    super();
    this.input = React.createRef();//
}
componentDidMount() {
    this.input.current.style.color='pink'//修改DOM节点的样式
}
handleSubmit=(e)=>{
    alert('测试看看'+this.input.current.value) //获取DOM节点的值
    e.preventDefault();//取消触发默认行为
}
render() {
    return (
       <input type="text" ref={this.input}/>
       <input type="submit" value="submit" onClick={this.handleSubmit}/>
    )
}
 

8. 事件处理

8.1 this问题
  • react中的函数方法和vue中使用的不一样,react中方法的this指向的undefined,这不是React的原因,这是JavaScript中本来就有的。如果你传递一个函数名给一个变量,然后通过在变量后加括号()来调用这个方法,此时方法内部的this的指向就会丢失。
    • 可以使用箭头函数,让该函数访问外部上下文
    • 可以使用bind(this)把外部的this绑定给该函数
    
    
    class componentName extends Component {
      constructor(props){
          super(props);
          //在构造函数中bind
          this.handleClick.bind(this)
      }
      //使用箭头函数,让该函数访问外部上下文
      decrement=()=>{
          this.setState({
              count:this.state.count-=1
          })
          console.log(this);
      }
      //在调用的时候使用bind绑定this
      handleClick(){
          console.log(this);
      }
      render() {
          return (
              <>     
                  <div className={myStyle.main} onClick={this.handleClick.bind(this)}>在调用的时候使用bind绑定this</div>
              </>   
          )
      }
    }
    
    
// 这个函数是有自己的作用域的,所以需要给他bind()一下,或者使用箭头函数修改this指向
   increment(){
        this.setState({
            // 由于这里的count是属于调用state中的count所以必须加上this.state
            count:this.state.count+=1
        })
        console.log(this);
   }
   decrement=()=>{
        this.setState({
            count:this.state.count-=1
        })
        console.log(this);
   }
    render() {
        return (
            <div>
                <h3>组件的state</h3>
                <div>这是state渲染的---》{this.state.count}</div>
                <button onClick={this.increment.bind(this)}>增加</button>
                <button onClick={ this.decrement }>减少</button>
            </div>
        );
    }
8.2 条件渲染
  • 条件渲染

    • react不像vue的写法,在标签中添加一个v-if的属性就可以使用判断。react的用法是类似js一样的。if...else和三元运算符
     render() {
    const {dataList} = this.state
    //条件渲染,if ... else模式
    let myDiv = ''
    if (true) {
    //赋值给变量的标签内容都可以在 render函数的渲染的时候,以html方式依次渲染出来
      myDiv = (
            <div>
              测试 上
              <p>1111</p>
              <span>2222</span>
              <img src="http://pic1.win4000.com/wallpaper/2020-10-21/5f8f95f2a49e2.jpg" alt=""/>
            </div>
        )
    }else{
      myDiv = <div>测试 下</div>
    }
    return (
      <div>
        <ul>
          {myDiv} // 变量判断渲染出来的元素
          {
            // 条件渲染三元运算(中间利用了循环遍历)
            dataList.length ?
              dataList.map((item,index)=>{
                return <li key={index}>{item.title}</li>
                })
            :
              <div>当前没有数据</div>
          }
        </ul>
      </div>
    )
    }
    
  • 循环遍历

    • react的循环遍历渲染和vue小程序那些是有差异的,不像后者只需协商v-for这类的属性名就可以使用。由于react使用的是jsx语法,所以它的遍历渲染是类似js的循环遍历一样的
  this.state= {
      myList:[
          {
              name:'ime',
              age:10,
              sex:"男"
              ,jobs:['111','222','333']
          }
      ]
  }
 <ul>
      {
          this.state.myList.map((element,index)=>{
              return(
                  <li key={index}>
                      <span>{element.name}</span>
                      <span>{element.age}</span>
                      <span>{element.sex}</span>
                      <span>{
                          element.jobs.map((e,i)=>{
                              return <p style={{color:'pink'}}>e</p>
                          })
                      }</span>
                  </li>
              )
          })
      }
  </ul>

9. State

  • 这个和微信小程序中的data或Vue的data差不多,主要是存数据,数据交互。存数据的方式和小程序很类似。
  • React的this.setState 和小程序中的this.setData 是很类似的,接触过小程序的对这个存储方式并不陌生,反而和vue的直接赋值不一样。
9.1 setState更新是同步还是异步
  • setState会引起视图的重绘
  • 在可控的情况下是异步,非可控的情况下是同步
如果直接使用,数据还是原来的值,因为是异步的。至于为什么设计为异步,因为异步操作也是有好处的。
        1. 可以显著的提升性能
            如果每次调用setState都进行一次更新的话,render函数是会被频繁调用的,界面不断的被重新渲染,
            这样会消耗大量的资源,导致效率变低。最好就是一次性获取多个数据,然后再批量的更新
        2. 如果同步更新state,但是还没有调用render函数,那么state和props不能保持同步state和props不能保持一致性,
            会在开发的过程中产很多问题

        改变DOM同时又要获取最新的值
        1. setState有一个回调函数,在回调函数中可以取到更新后的最新值
            changeNumber(){
                this.setState({
                    message:'哈哈'
                },()=>{
                    console.log(this.state.message)
                })
            }
        2. 通过componentDidUpdate函数,组件的更新会触发它,此时就是同步的新数据
            componentDidUpdate(prevProps, prevState) {
                console.log(this.state.message)
            }

        3. 使用async await也是可以改为同步
            async hanlerAdd(){
                await this.setState({
                    count:this.state.count+=1
                })
                console.log(this.state.count);
            }
        
        4. 使用原生DOM操作,默认也是同步
            <button id="btn">减</button>
            hanlerAdd(){
                document.querySelector('#btn').addEventListener("click",()=>{
                this.setState({
                    message:'哈哈'
                })
                console.log(this.state.message)
            })

10. React生命周期函数

生命周期在现在开发中是随处可见的,和小程序、vue中的是差不多的。 函数列表:

  • componentWillMount

    • 组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
    • 和小程序、vue、uniapp的 onLoad 差不多
  • render

    • react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
  • componentDidMount

    • 组件渲染之后调用,只调用一次。可以在此请求数据
  • componentWillReceiveProps

    • 组件初始化时不调用,组件接受新的props时调用。
  • shouldComponentUpdate

    • 数据在改变之前执行(state,props)
    • react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
  • componentWillUpdata(nextProps, nextState)

    • 组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state

11.PropsTypes 类型检测

  • 类似于ts的类型检测,使程序更加健壮。类型检测的好处在于,随着程序越来越大,可以通过它捕获大量的错误。
  • PropsTypes给props做类型检测
import React, { Component } from 'react';
import PropsType from 'prop-types';
export default class PropsTypeDemo extends Component {
    render() {
        return (
            <div>
                {this.props.title}
            </div>
        );
    }
}
PropsTypeDemo.propTypes = {
    title:PropsType.bool
}
// 如果传递的数据不对,则报错  Failed prop type:
  • props types提供了几种类型的校验
// 声明属性为js原生类型,这些属性都是可选的
 optionalArray: PropTypes.array,
 optionalBool: PropTypes.bool,
 optionalFunc: PropTypes.func,
 optionalNumber: PropTypes.number,
 optionalObject: PropTypes.object,
 optionalString: PropTypes.string,
 optionalSymbol: PropTypes.symbol,

 //任何可被渲染的元素,包含数字、字符串、元素和数组
 optionalNode: PropsTypes.node,
 
 // 一个React元素
 optionalElement: PropsTypes.element,
 
 //一个React元素类型 
 optionalElementType:PropsTypes.elementType,
 
 //一个对象可以是几种类型中的任意一个类型
 optionalUnion: PropsTypes.oneOfType([
 	PropsTypes.string,
    PropsTypes.number,
    PropsTypes.instanceOf(Message)
 ])
 
 //任意类型的必需数据
 requiredAny: PropsTypes.any.isRequired,
 
 //自定义验证器,失败时返回Error对象。但是在 console.warn和抛出异常中是不可用的,onOfType 不会生效
 customProp:function(props, propName, componentName){
 	if(!/matchme/.test(props[propName])){
    	return new Error(
        	`验证信息有误`
        )
    }
 }
 
 ### 更多的用法可以查看官方文档

12. 路由

路由好比是打车,只要说出了特定的地名,老司机就能带你到那个地方。

  • history 模式
    • window 的 history 提供了对浏览器历史记录的访问功能,并且它暴露了一些方法和属性,让你在历史记录中自由的前进和后退,并且在 H5 中还可以操作历史记录中的数据。
    history.back() ;	// 在历史记录是后退
    history.forward();      // 在历史记录中前进
    history.go(-1);	// 移动到指定的历史记录点
    
  • hash 模式
    • hash的改变,不会导致浏览器的刷新

13. 网络请求,Fecth

  • fetch和axios都是基于promise设计的,可以先把promise的知识学一学有助于对这个库的理解更加透彻。
  • fetch写起来代码会更加简洁(这里是使用了es6的箭头函数以及 promise的 .then 来写的)
componentDidMount() {
// GET请求
fetch("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php").then(res => res.json())
  .then(data =>{
    if( data.success ){ //这里是后端返回的成功码
      console.log(data);
    }
  }).catch(err => console.log('错误信息',err))
 
 /* POST请求
      * Ajax的参数是对象键值对的
      * Fetch的body 是字符串类型
      	* "user_id=123123&password=123123&verification_code=123123"
        Fetch识别的是这样子的类型请求参数
     * import qs from "querystring"; 
     	* 引入nodeJs的对象转换字符串方法
*/
   fetch("http://iwenwiki.com/api/blueberrypai/login.php",{
      method:"POST",
      body: qs.stringify({ // 引入了nodeJs的对象转字符串方法 qs.stringify
        user_id:123123,
        password:123123,
        verification_code:123123
      }),
      headers:{
        "Content-Type":'application/x-www-form-urlencoded',
        "Accept":"application/json,text/plain,*/*"
      }
   }).then(res=>res.json())
   .then(data=>{
    console.log(data);
   }).catch(err => console.log(err))
  }
 }
  • 网络请求的过程中是会出现跨域问题的,端口不同就会出现跨域问题,一般这种问题可以让后端去解决。不过前端也是有解决的方法。

    • 步骤一 npm install http-proxy-middleware
    • 步骤二 npm run eject
    • 步骤三 src下创建一个 setupProxy.js文件
    //0.X 版本的引入这个
    //const proxy = require('http-proxy-middleware');
    //1.X 版本的引入这个
    const { createProxyMiddleware } = require("http-proxy-middleware");
    module.exports = function(app) {
      app.use(
        createProxyMiddleware("/api", {
          target: "http://localhost:3000",
          changeOrigin: true
        })
      );
    };
    
    • 步骤四 start.js里面做一下配置
    // 在scripts文件夹里面的start.js 搜索 devServer
    const serverConfig = createDevServerConfig(
      proxyConfig,
      urls.lanUrlForConfig
    );
    const devServer = new WebpackDevServer(compiler, serverConfig);
    //在它下面添加上即可
    require('../src/setupProxy')(devServer);//文件路径按自己的来