React基础+脚手架笔记

222 阅读26分钟

初识

react是一个将数据渲染为HTML视图的开源JavaScript库

React的优点

  1. 采用组件化模式,声明式编码,提高开发效率及组件复用率
  2. 在React Native中可以使用React语法进行移动端开发(React Native可以编写苹果安卓应用)
  3. 使用虚拟dom+优秀的Diffing算法,尽量减少与真实DOM的交互,最小化页面重绘

Hello React

babel.js 用来转换js和jsx 可以通过 CDN 获得 React 和 ReactDOM 的 UMD 版本。

引入顺序要主意 引入核心库 dom babel

<!-- 准白好一个容器 -->
<div id="test"></div>

<!-- 引入核心库 -->
<script src="../react.development.js"></script>
<!-- 引入react-dom用于支持react操作DOM -->
<script src="../react-dom.development.js"></script>
<script src="../babel.min.js"></script>
<script type="text/babel"> //此处必须写成Babel
  //创建虚拟dom
  const Virtual = (  //此处一定不要写引号 因为是虚拟dom
    <h1>hello,react</h1>
  )
  //渲染虚拟dom到页面
  // ReactDOM.render(虚拟DOM,容器)
  //ReactDOM.render(Virtual,"#test") //react没有提供这样的写法
  ReactDOM.render(Virtual,document.getElementById('test'));
</script>

js和jsx 区别

使用js来创建虚拟dom
  const VDOM = React.createElement("h1, {id:"title"}, React.createElement("span"));
  
  
使用jsx 会自动转换成上面的样子 其实就是语法糖
  const Virtual = ( 
    <h1>
		<span id="title"></span>
    </h1>
  )

虚拟dom 注释

  const Virtual = ( 
    <h1>
		{ /* <span id="title"></span> */ } 
		{变为代码块}  {/* 代码块里就放个注释 */}
    </h1>
  )


  const Virtual = ( 
    <h1>
		<span id="title"></span>
    </h1>
  )
  
  
  虚拟dom Virtual就是一个对象
  
  debuger测试看来说
  虚拟dom比较轻 因为很多用不上

jsx语法规则javascript xml

jsx本质是语法糖const VDOM = React.createElement("h1, {id:"title"},React.createElement("span"));

标签混入js表达式时要用{} -----何为表达式?何为语句?

  const myId = 'aTguiGu'
  const myData = 'hello,ReaCt'
  const Virtual = (
    <h1 id={myId.toLocaleLowerCase()}> //标签混入js表达式时要用{}
      <span>{myData.toLocaleLowerCase()}</span>  
    </h1>
  )
  ReactDOM.render(Virtual,document.getElementById('test'));
  
  
  表达式
  a
  a+b
  demo(1)
  arr.map()
  function(){}
  
  const i = ?能接到值就是表达式
  
  
  语句
  if(){}
  for(){}
  switch(){}
  
  const i = 接不到值

jsx中样式class要写className 因为和js中class类冲突


  const Virtual = (  //此处一定不要写引号 因为是虚拟dom
    <h1 className="title" id={myId.toLocaleLowerCase()}>
      <span>{myData.toLocaleLowerCase()}</span>  
    </h1>
  )

内联样式需要双括号 小驼峰 sytle={{key:value,key:value}}

  const Virtual = (  //此处一定不要写引号 因为是虚拟dom
    <h1 className="title" id={myId.toLocaleLowerCase()}>
      <span style={{color:'red',fontSize:'50px'}}>{myData.toLocaleLowerCase()}</span>  
    </h1>
  )

只能有一个根标签 标签必须闭合

标签首字母

若小写字母开头,则将标签转为html中同名元素,若html没有对应元素 则报错

若大写字母开头,直接找对应组件

小练习 渲染数组的值 react会自动遍历数组

react会自动帮你遍历数组

  const data = ['Angular','React','Vue']
  const Virtual = (
    <div>
      <h1>前端框架</h1>
      <ul>
        {
          data.map((item,index)=>{ //只能写表达式
            return <li key={index}>{item}</li>
          })
        }
      </ul>  
    </div>
  )
  ReactDOM.render(Virtual,document.getElementById('test'));

面向组件编程

谷歌网上商店下载安装react dev tools

控制台就可以看组件和测试工具

定义组件的方法

函数式组件

函数必须有返回值 挂在必须为结束标签

  // 创建函数式组件
  function MyComponent() {
    return <h2>我是函数定义组件(适用于【简单组件】的定义)</h2>  //必须有返回值
  }
  					//必须为结束标签
  ReactDOM.render(<MyComponent/>,document.getElementById('test'));

class类的使用

//创造一个Person类
class Parson{
  //构造器
  constructor(name,age){
    this.name = name
    this.age = age
  }
  //一般方法
  speak(){
    //speak写在原形链上面
    console.log(`我叫${this.name},我今年${this.age}岁`);
  }
}


//创建一个学生类 继承parson
class Student extends Parson{
  //没有构造器 直接搬过来继承的构造器
}

const s1 = new Student("小张",12)
console.log(s1);

如果继承以后有新的属性要用构造器

//创造一个Person类
class Parson{
  //构造器
  constructor(name,age){
    this.name = name
    this.age = age
  }
  //一般方法
  speak(){
    //speak写在原形链上面
    console.log(`我叫${this.name},我今年${this.age}岁`);
  }
}


//创建一个学生类 继承parson
class Student extends Parson{
  constructor(name,age,grade){
    super(name,age)   //super只能写到第一行
    this.grade = grade
  }
}

const s1 = new Student("小张",12,"高一")  //比parson多了一个年级
console.log(s1);

类中的构造器不是必须的 不写也可以 类中的方法都放到了原型对象上

类式组件

类必须继承React内置类 必须有render方法 render必须有返回值



  // 创建类式组件
  class MyComponent extends React.Component{  //必须继承
    render(){  //render方法
      return <h2>我是类定义组件(适用于【复杂组件】的定义)</h2>  //返回值
    }
  }
  //渲染组件到页面
  ReactDOM.render(<MyComponent/>,document.getElementById('test'))
  
  /*
  class MyComponent类里面的render是供实例使用
  但是并没有new一个实例
  当渲染的时候 react会自动new一个实例 并调用render方法
  */
  
  
  
  // 创建类式组件
  class MyComponent extends React.Component{
    render(){
      // render放在那?--MyComponent的原形对象上,供实例使用
      // rander中的this是谁?--MyComponent的实例对象 或组件实例对象
      console.log(this);
      return <h2>我是类定义组件(适用于【复杂组件】的定义)</h2>
    }
  }
  //渲染组件到页面
  ReactDOM.render(<MyComponent/>,document.getElementById('test'))

$state-组件实例的核心属性- 对state(状态)的理解

函数式组件=简单组件=无state 类式组件=复杂组件=有state

解决changeWeather中this指向问题

class Weather extends React.Component{
  constructor(props){
    super(props)
    //初始化状态
    this.state = {isHot:true}
  }
  render(){
    //读取状态
    const {isHot} = this.state
    		//这里必须写this.changeWeather因为直接写拿不到
    return <h1 onClick={this.changeWeather.bind(this)}>今天天气很{isHot?"炎热":"寒冷"}</h1>
  }
  changeWeather(){
  	//局部会自动开启严格模式 所以上面不写bind的话this指向undefined
    console.log(this.state);
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

或者以下 构造器里的this就是实例对象

class Weather extends React.Component{
  constructor(props){
    super(props)
    //初始化状态
    this.state = {isHot:true}
    this.changeWeather = this.changeWeather.bind(this) //构造器里的this就是实例对象  这里挂上 点击输出的就是自身的changeWeather
  }
  render(){
    //读取状态
    const {isHot} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot?"炎热":"寒冷"}</h1>
  }
  changeWeather(){
    console.log(this.state);
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

setState的使用 类似小程序

通过上面解决了指向问题 现在要点击变换true和false

class Weather extends React.Component{
  constructor(props){
    super(props)
    //初始化状态
    this.state = {isHot:true}
    this.changeWeather = this.changeWeather.bind(this)
  }
  render(){
    //读取状态
    const {isHot} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot?"炎热":"寒冷"}</h1>
  }
  changeWeather(){
  	//这里确实改了 但是页面不会炎热寒冷一直切换
    this.state.isHot = !this.state.isHot
    console.log(this.state.isHot);
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

因为状态里的东西不能直接更改,找到那个属性直接改就是直接更改


class Weather extends React.Component{
  constructor(props){
    super(props)
    //初始化状态
    this.state = {isHot:true}
    this.changeWeather = this.changeWeather.bind(this)
  }
  render(){
    //读取状态
    const {isHot} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot?"炎热":"寒冷"}</h1>
  }
  changeWeather(){
    //严重注意state状态不可直接更改
    // this.state.isHot = !this.state.isHot //错误写法
    // console.log(this.state.isHot);

    //由于上面写了this.changeWeather = this.changeWeather.bind(this)
    //则这里面的this就是Weather的实例对象
    //实例对象的原型上面有render 有changeWeather 原型的原型是React.Component 里面有个方法叫setState
    this.setState({
      isHot:!this.state.isHot
    })
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

整体结构的小总结 精简方式

小总结

class Weather extends React.Component{
  constructor(props){   //只会调用一次 生成实例的时候
    super(props)
  }
  render(){} //1+n次 先调用把组件return出去 然后当事件函数先触发修改状态 重新渲染则在调用
  changeWeather(){} //触发几次执行几次
}
ReactDOM.render(<Weather/>,document.getElementById("test"))





class Weather extends React.Component{
  constructor(props){  //构造器为啥要写?
    super(props)
    //必须初始化状态
    this.state = {
      isHot:true,
      wind:"微风"
    }
    //解决this指向问题
    this.changeWeather = this.changeWeather.bind(this)
  }
  render(){ //render里的this就是组件实例对象
    //读取状态
    const {isHot,wind} = this.state
    //做展示
    return <h1 onClick={this.changeWeather}>今天天气很{isHot?"炎热":"寒冷"}</h1>
  }
  changeWeather(){
    this.setState({
      isHot:!this.state.isHot //获取值 更新值
    })
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

精简state

class Weather extends React.Component{
  constructor(props){}  //this肯定指向实例对象
  render(){}		//react内部new出来实例对象 会自动调用render所以this也是实例对象
  changeWeather(){} // this是undefined 内部自动严格模式
}

constructor里面只有两件事 解决this指向 初始化状态
类里面可以直接写赋值语句 但是必须死值
class Weather extends React.Component{
  constructor(props){}
  render(){}
  changeWeather(){}
  a = 1
}
new出来的实例就会有a=1的属性 等价于在constructor里面写了
this.a = 1

所以直接精简state
class Weather extends React.Component{
  constructor(props){}
  state = {name:"xxx"}
  render(){}
  changeWeather(){}
}

精简constructord的this指向问题

class Weather extends React.Component{
  constructor(props){}
  render(){}
  changeWeather = function(){} //这样写就是赋值语句 和a=1一个意思
  //本来这个方法是在原型上 这样就直接到了实例的属性上
  //光这样写还是不行 仅仅是换了个地方 原型到自身而已
}


class Weather extends React.Component{
  constructor(props){}
  render(){}
  //箭头会拿外部的this
  log(this)?? //类里面不能写函数体 所以不必纠结 既然箭头拿外部this 直接箭头里输出this
  changeWeather = ()=>{
  	console.log(this) //为weather的实例对象
  }
}

最终结构 注意要点

class Weather extends React.Component{
  //初始化状态
  state = {
    isHot:true,
    wind:"寒冷"
  }
  
  render(){
    const {isHot,wind} = this.state
    return <h1 onClick={this.changeWeather}>今天天气很{isHot?"炎热":"寒冷"},{wind}</h1>
  }
  
  //自定义方法--赋值语句+箭头函数
  changeWeather = ()=>{
    this.setState({
      isHot:!this.state.isHot
    })
  }
}
ReactDOM.render(<Weather/>,document.getElementById("test"))

注意要点

  1. 组件中render的this为组件实例对象 (因为react自动new并调用)
  2. 组件自定义方法中this为undefined(因为类中自动为严格模式) 并且事件调用时没有this a. 强制绑定this 通过bind() (bind不同于call不会执行) b.通过箭头函数
  3. 状态数据不能直接修改 必须拿到实例原型的原型React里面的setState来改

$props-组件实例的核心属性- props

基本使用 简单传值

//创建组件
class Parson extends React.Component{
  state = { name:'tom', age:18, sex:'女' }
  render(){
    const {name,age,sex} = this.state
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{age}</li>
        <li>年龄:{sex}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Parson/>,document.getElementById('app1'))
ReactDOM.render(<Parson/>,document.getElementById('app2'))
ReactDOM.render(<Parson/>,document.getElementById('app3'))

这是基本写法,但是现在 我需要从外部拿到数据 来渲染 上面这种写法像是自家的事情 而且渲染的三个人都是一个名字性别年龄

//创建组件
class Parson extends React.Component{
  render(){
    console.log(this);
    const {name,age,sex} = this.props
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{age}</li>
        <li>年龄:{sex}</li>
      </ul>
    )
  }
}
ReactDOM.render(<Parson name='tom' age='18' sex='女' />,document.getElementById('app1'))
			//这里react会自动把name:tom传到props里面

批量传递props 比如信息很多

批量传递props 批量传递标签属性

    //创建组件
    class Parson extends React.Component {
      render() {
        console.log(this);
        const { name, age, sex } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{age}</li>
            <li>年龄:{sex}</li>
          </ul>
        )
      }
    }
    const result = { name: "老刘", age: 18, sex: "女" }
    ReactDOM.render(<Parson name={result.name} age={result.age} sex={result.sex} />, document.getElementById('app1'))
    //nonono如果数据太多 则不能这样写
    					//这样写
    ReactDOM.render(<Parson {...result} />, document.getElementById('app2'))
    
    
    
    
    
测试。。。
var a = {n: 1, b: 2, c: "你好"}
var b = {say:"hello"}
var obj = {...b,...a} 或者 var obj = new Object({...b,...a})

obj的值为{say: "hello", n: 1, b: 2, c: "你好"}

对props进行限制propTypes

限制必须写到类上面 而不是实例上面

    //创建组件
    class Parson extends React.Component {
      render() {
        console.log(this);
        const { name, age, sex } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{age}</li>
            <li>年龄:{sex}</li>
          </ul>
        )
      }
    }
    //固定写法 react每次new都会去问Parson类身上有没有这个东西
    Parson.propTypes = {
      //开头大写的P,这是react的内置属性
      name:React.propTypes.string  //类型也为小写
    }
    const result = { name: "老刘", age: 18, sex: "女" }
    ReactDOM.render(<Parson name={result.name} age={result.age} sex={result.sex} />, document.getElementById('app1'))
    ReactDOM.render(<Parson {...result} />, document.getElementById('app2'))
    

限制规则

    Parson.propTypes = {
      //开头大写的P,这是react的内置属性
      name:React.propTypes.string  //类型也为小写
    }
    但是这种写法在16.xx后被弃用 因为react上带着propTypes会变的很大 
    而且有时候也不一定非要进行限制
    
    
    16.xx以后 就是新弄一个一个依赖包来进行 需要就载入prop-types.js
	用来对组件标签进行限制 引入则多了一个对象 PropTypes
	直接在类.PropTypes里写
	    //固定写法 react每次new都会去问Parson类身上有没有这个东西
    Parson.propTypes = {
      //开头大写的P,这是react的内置属性
      name:PropTypes.string.isRequired,  //类型也为小写 必须填写
      sex:propTypes.string,
    }

默认值

    //指定默认值
    Parson.defaultProps = {
      sex:"不男不女",
      age:18
    }
    
    

如果穿的是方法 则限制改为handel:PropTypes.func

    //创建组件
    class Parson extends React.Component {
      render() {
        const { name, age, sex,handel } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{age}</li>
            <li>年龄:{sex}</li>
          </ul>
        )
      }
    }
    //固定写法 react每次new都会去问Parson类身上有没有这个东西
    Parson.propTypes = {
      //开头大写的P,这是react的内置属性
      name:PropTypes.string.isRequired,  //类型也为小写 必须填写
      sex:PropTypes.string,
      age:PropTypes.number,
      handel:PropTypes.func //传方法
    }
    //指定默认值
    Parson.defaultProps = {
      sex:"不男不女",
      age:18,
    }
    function handle(){}
    const result = { name: "老刘",sex:"男", age: 18,handle:handle}
    ReactDOM.render(<Parson {...result} />, document.getElementById('app2'))

propType的简写方式 props只读不能改

props是只读的不能this.props等于xxx

      render() {
        const { name, age, sex,handel } = this.props
        this.prop.xxx = xxx //报错
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{age}</li>
            <li>年龄:{sex}</li>
          </ul>
        )
      }

简写

    class Parson extends React.Component {
      render() {
        const { name } = this.props
        return (
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{age}</li>
            <li>年龄:{sex}</li>
          </ul>
        )
      }
      static propTypes = { //直接写进去是写到了实例对象上 并不是类上面 必须在前面加static静态的
        name: PropTypes.string.isRequired
      }
      static defaultProps = {
        sex: "不男不女"
      }
    }
    
    Parson.propType = {  //如果把这个直接写进去 得加static
        name: PropTypes.string.isRequired,
    }
    
    
    
    
    
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Foo {
  static say() {
    return 'hello';
  }
}
Foo.say() // 'hello'

var foo = new Foo();
foo.say() // TypeError: foo.classMethod is not a function

注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

类式组件中的构造器与props

React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。


      constructor(props){
        super() //这里不传的话
        console.log(props);
        console.log(this.props); //那么这里就有可能拿不到 直接为undefined
      }

函数式组件的props

函数因为可以拿参数所以可以有props state只能后面学到hooks refs不可以

    function Parson(props) {
      const { name, sex, age, } = props
      return (
        <ul>
          <li>姓名:{name}</li>
          <li>性别:{age}</li>
          <li>年龄:{sex}</li>
        </ul>
      )
    }
    Parson.propTypes = {
      name: PropTypes.string.isRequired,
      sex: PropTypes.string,
      age: PropTypes.number,
      handel: PropTypes.func
    }
    Parson.defaultProps = {
      sex: "不男不女",
      age: 18,
    }
    function handle() { }
    const result = { name: "老刘", age: 18, handle: handle }



    ReactDOM.render(<Parson {...result} />, document.getElementById('app1'))

props总结

//组件内部不要修改props的值 为只读


//可以使用{...obj}来批量传递
ReactDOM.render(<Parson {...result} />, document.getElementById('app1'))


//进行props限制
类名.defaultProps = {
      sex: "不男不女",
      age: 18,
    }
或者写在类里面加static


//函数式组件也可以用props

$refs-组件实例的核心属性 与 事件处理

字符串形式的ref-已不被推荐使用16.8还能用

首先有一个需求 两个input框 第一个输入内容点击按钮则弹出显示内容 第二个输入内容失去焦点则弹出显示内容


		//基本写法
    class Parson extends React.Component {
      render() {
        return (
          <div>		//这里加了id
            <input id="i1" type="text" placeholder="点击按钮输出数据" />
            <button onClick={this.btn}>点我弹出左侧数据</button>
            <input type="text" placeholder="失去焦点输出数据" />
          </div>
        )
      }
      btn = () => {
        const i = document.getElementById("i1")  //可是都用了react没必要这样写
        alert(i.value)
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))
    
    
    
    
   		//使用react里的ref
    class Parson extends React.Component {
      render() {
        return (
          <div>		//使用ref
            <input ref="input1" type="text" placeholder="点击按钮输出数据" />
            <button onClick={this.btn1}>点我弹出左侧数据</button>
            <input ref="input2" onBlur={this.btn2} type="text" placeholder="失去焦点输出数据" />
          </div>
        )
      }
      btn1 = () => {
        console.log(this.refs.input1.value) //这里用refs
      }
      btn2 = () => {
        console.log(this.refs.input2.value)
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

回调形式的ref 调用次数的小问题

标签必须用ref关键字

    class Parson extends React.Component {
      render() {
        return (
          <div>  //往外找this 找到render 找到实例
            <input ref={c => this.input1 = c} type="text" placeholder="点击按钮输出数据" />
            <button onClick={this.btn1}>点我弹出左侧数据</button>
            <input ref={(c) => {this.input2 = c}} onBlur={this.btn2} type="text" placeholder="失去焦点输出数据" />
          </div>
        )
      }
      btn1 = () => {
      	console.log(this.input1.value);
      }
      btn2 = () => {
      	console.log(this.input2.value);
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

回调形式直接写回调函数 this指向recder指向实例

调用次数的小问题 回调函数调用次数 问题 当写为回调函数 且状态被修改后 react会再次调用render 回调函数会重置为null在赋值为你写的函数

当代码如下时

    class Parson extends React.Component {
      state = {
        isHot: true
      }
      render() {
        const { isHot } = this
        return (
          <div>
            <input ref={c => { this.input1 = c; console.log("输出了", c); }} type="text" />
            <button onClick={this.btn1}>点我弹出左侧数据</button>
            <h1 onClick={this.handleH1}>今天天气{isHot ? "很热" : "寒冷"}</h1>
          </div>
        )
      }
      btn1 = () => {
        console.log(this.input1.value);
      }
      handleH1 = () => {
        this.setState({
          isHot:!this.isHot
        });
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

log会在页面生成的时候输出一句输出了 因为react默认执行一次render

当点击和标签时 输出结果如下: 输出了 null 输出了 因为当状态改变reder重新运行 会初始化为null 在运行函数

解决方案 挂载到自身的函数中 class中的绑定函数 就可以避免 但是大多数情况下 是无关紧要的

    class Parson extends React.Component {
      state = {
        isHot: true
      }
      render() {
        const { isHot } = this
        return (
          <div>
            <input ref={this.saveInput} type="text" />
            <button onClick={this.btn1}>点我弹出左侧数据</button>
            <h1 onClick={this.handleH1}>今天天气{this.state.isHot ? "很热" : "寒冷"}</h1>
          </div>
        )
      }
      saveInput = (c)=>{
        this.input1 = c
        console.log("输出",c);
      }
      btn1 = () => {
        console.log(this.input1.value);
      }
      handleH1 = () => {
        this.setState({
          isHot:!this.state.isHot
        });
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

createRef的使用

    class Parson extends React.Component {
      myRef1 = React.createRef() //调用后可以返回一个容器 该容器可以存储被ref标识的节点
      //等于我弄了个容器挂在的实例自身 起名为myRef
      //但是该容器 专人专用 里面只能存一个
      myRef2 = React.createRef()
      state = {
        isHot: true
      }
      render() {
        const { isHot } = this
        return (
          <div>
            <input ref={this.myRef1} type="text" />
            {/*react发现你放的是React.createRef()创造的容器 就把当前的节点存到容器里*/}
            <input ref={this.myRef2} onBlur={this.handle2} type="text" />
            <button onClick={this.handle1}>点我弹出左侧数据</button>
          </div>
        )
      }
      handle1 = () => {
        console.log(this.myRef1.current); //这里的current是react给你生成的
      }
      handle2 = () => {
        console.log(this.myRef2.current);
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

例子2

    class Parson extends React.Component {

      myRef = {
        myRef1:React.createRef(),
        myRef2:React.createRef()
      }
      state = {
        isHot: true
      }
      
      render() {
        const { isHot } = this.state

        //这里尝试结构结果不行
        //只能一个一个
        const {myRef1,myRef2} = this.myRef
        return (
          <div>
            <input ref={myRef1} type="text" />
            {/*react发现你放的是React.createRef()创造的容器 就把当前的节点存到容器里*/}
            <input ref={myRef2} onBlur={this.handle2} type="text" />
            <button onClick={this.handle1}>点我弹出左侧数据</button>
          </div>
        )
      }
      handle1 = () => {
        console.log(this.myRef.myRef1.current); //这里的current是react给你生成的
      }
      handle2 = () => {
        console.log(this.myRef.myRef2.current);
      }
    }
    ReactDOM.render(<Parson />, document.getElementById('app1'))

事件处理 可以用target的时候少用refs

  1. 通过onXxx属性指定事件处理函数(注意大小写) a. React使用的是自定义(合成)事件,而不是使用的元素DOM事件 -- 为了更好的兼容性封装过 b. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) -- 为了高效
  2. 通过event.target得到发生事件的DOM元素对象

收集表单数据

受控 非受控组件

受控组件 现用现取 输入类的dom随着你的输入维护到state里 需要的时候直接从状态里取

    class Login extends React.Component {

      state = {
        username:'',
        password:''
      }
      render() {
        return (
          <div>
            <form action="" onSubmit={this.handleSubmit}>
              用户名<input onChange={this.saveUsername} type="text" name="username" />
              密码<input onChange={this.savePassword} type="password" name="password" />
              <button>登陆</button>
            </form>
          </div>
        )
      }
      saveUsername = (e) => {
        this.setState({
          username:e.target.value
        })
      }
      savePassword = (e) => {
        this.setState({
          password:e.target.value
        })
      }
      handleSubmit = (e) => {
      	//这里打印完会转跳到form里面的action的地址
        e.preventDefault() //阻止默认事件 阻止表单提交
        const {username,password} = this.state
        console.log(username,password);
      }
    }
    ReactDOM.render(<Login />, document.getElementById('app1'))

非受控 用ref取到节点当前的值

很多的使用柯里化来写

如果我想实现登陆可以这样写

    class Login extends React.Component {

      state = {
        username:'',
        password:''
      }
      render() {
        return (
          <div>
            <form action="" onSubmit={this.handleSubmit}>
              用户名<input onChange={this.saveUsername} type="text" name="username" />
              密码<input onChange={this.savePassword} type="password" name="password" />
              <button>登陆</button>
            </form>
          </div>
        )
      }
      saveUsername = (e) => {
        this.setState({
          username:e.target.value
        })
      }
      savePassword = (e) => {
        this.setState({
          password:e.target.value
        })
      }
      handleSubmit = (e) => {
        e.preventDefault() //阻止默认事件 阻止表单提交
        const {username,password} = this.state
        console.log(username,password);
      }
    }
    ReactDOM.render(<Login />, document.getElementById('app1'))

但是如果我要注册 手机很多的信息 这样的写法就太臃肿了

第一种方法 事件触发时传进去
    class Login extends React.Component {

      state = {
        username: '',
        password: ''
      }
      render() {
        return (
          <div>
            <form action="" onSubmit={this.handleSubmit}>
              用户名<input onChange={(e)=>this.saveFormData('username',e)} type="text" name="username" />
              密码<input onChange={(e)=>this.saveFormData('password',e)} type="password" name="password" />
              <button>登陆</button>
            </form>
          </div>
        )
      }
      //保存表单数据到state中
      saveFormData = (v,e) => {
        console.log(v,e.target.value);
        this.setState({
          [v]:e.target.value //这里要加[]要不然会当你写的是'v' 字符串 不会读变量数据
        })
      }
    }
    ReactDOM.render(<Login />, document.getElementById('app1'))
    
    
    -------------------------------------------------------------------
    //第二种刚开始就执行传进去username 函数柯里化
    class Login extends React.Component {

      state = {
        username: '',
        password: ''
      }
      render() {
        return (
          <div>
            <form action="" onSubmit={this.handleSubmit}>
            					//页面刚开始就会执行 把username传过去
              用户名<input onChange={this.saveFormData('username')} type="text" name="username" />
              密码<input onChange={this.saveFormData('password')} type="password" name="password" />
              <button>登陆</button>
            </form>
          </div>
        )
      }
      //触发事件时在执行return里的 
      saveFormData = (v) => {
        //console.log(v);
        return (e) => {
          console.log(v,e.target.value);
          this.setState({
            [v]:e.target.value
          })
        }
      }
    }
    ReactDOM.render(<Login />, document.getElementById('app1'))

组件生命周期

基本使用

基本的 componentDidMount componentWillUnmount 卸载组件ReactDOM.unmountComponentAtNode(document.getElementById('app1'))

    class Life extends React.Component {
      // 状态
      state = {
        opacity: 1
      }
      //初始化时 状态更新时
      render() {
        const { opacity } = this.state
        return (
          <div>
            <h2 style={{ opacity: opacity }}>学不会React怎么办?</h2>
            <button onClick={this.death}>不行!</button>
          </div>
        )
      }
      //生命周期 组件挂载完毕时调用 不需要等于箭头函数 因为不是拿来用作回调
      //组件  完成   挂载
      componentDidMount() {
        this.timer = setInterval(() => {
          let { opacity } = this.state
          opacity += 1
          if (opacity <= 0) opacity = 1
          this.setState({
            opacity
          })
        }, 200);
      }
      //组件  即将  卸载
      componentWillUnmount() {
        clearTimeout(this.timer)
      }
      death = () => {
        //也可以在这里关闭定时器

        //卸载组件
        ReactDOM.unmountComponentAtNode(document.getElementById('app1'))
      }
    }
    //渲染到页面挂载到页面mount 卸载unmount
    ReactDOM.render(<Life />, document.getElementById('app1'))

组件的顺序

constructor
componentWillMount  组件将要挂载
render
componentDidMount  组件挂载完毕


使用ReactDOM.unmountComponentAtNode()卸载之前会调这个函数
componentWillUnmount 组件即将卸载

当调用setState()时

相当于组件更新的阀门 默认返回true 返回假则不往下走 返回真则继续往下走
shouldComponentUpdate( )

组件将要更新的钩子
componentWillUpdate

render

组件更新完毕的钩子
componentDidUpdate

当调用forceUpdate()强制更新时

比更新少了个阀门 不想对状态做出修改 就像让你更新一下就用这个

组件将要更新的钩子
componentWillUpdate

render

组件更新完毕的钩子
componentDidUpdate

总结代码

    //创建组件
    class Life extends React.Component {

      //构造器
      constructor(props) {
        super(props)
        console.log('Life-constructor');

        // 初始化状态
        this.state = {
          count: 0
        }
      }
      render() {
        console.log('Life-render');
        const {
          count
        } = this.state
        return (
          <div>
            <h2>当前求和为{count}</h2>
            <button onClick={this.add}>加一</button>
            <button onClick={this.force}>强制更新</button>
          </div>
        )
      }
      //组件将要挂载
      componentWillMount() {
        console.log('Life-componentWillMount');
      }
      //组件  完成   挂载
      componentDidMount() {
        console.log('Life-componentDidMount');
      }



      //------------------------------------------------------------------------------

      // 当setState调用 默认返回true 返回假则不往下走 
        				//下一步要变成的props和state
      shouldComponentUpdate(nextProps,nextState) {
        console.log('Life-shouldComponentUpdate');
        return true
      }
      // 组件将要更新的钩子
      componentWillUpdate() {
        console.log('Life-componentWillUpdate');
      }
      //组件更新完毕的钩子
      componentDidUpdate() {
        console.log('Life-componentDidUpdate');
      }

      //------------------------------------------------------------------------------



      // 当要卸载前
      componentWillUnmount() {
        console.log('Life-componentWillUnmount');
      }

      add = () => {
        let { count } = this.state
        this.setState({
          count: count + 1
        })
      }
      force = () => {
        this.forceUpdate()
      }
    }
    //渲染组件
    ReactDOM.render(<Life />, document.getElementById('app1'))

父组件render流程

先贴代码 A组件的name传给B组件展示

    class A extends React.Component {
      state = {
        carName: '奔驰'  //A组件只定义不展示
      }
      render() {
        return (
          <div>
            <div>A</div>  {/*未展示A状态里的汽车*/}
            <button onClick={this.changeCar}>换车</button>
            <B {...this.state} />
          </div>
        )
      }
      changeCar = () => {
        this.setState({
          carName: '奥托'
        })
      }
    }
    class B extends React.Component {
      componentWillReceiveProps(props){
        console.log('子组件将要接受标签属性(参数)componentWillReceiveProps',props);
      }
      render() {
        console.log(this.props);
        return (
          <div>
            <div>B</div>
            <div>{this.props.carName}</div> {/*B组件里展示A传过来的汽车*/}
          </div>
        )
      }
    }
    //渲染组件
    ReactDOM.render(<A />, document.getElementById('app1'))

生命周期

一旦父组件的render执行,那么子组件就会执行
组件   将要   接受  标签属性(参数)
componentWillReceiveProps(props)

但是你会发现 父组件的props第一次传过来(也就时页面第一次渲染)是不会调用的
再次该变父组件的props值 则就会调用这个钩子函数



然后就和更新状态一样
shouldComponentUpdate( ) 阀门 默认返回true

组件将要更新的钩子
componentWillUpdate

render

组件更新完毕的钩子
componentDidUpdate(preProps,preState)

新版本的生命周期

新版本即将废弃的钩子

以下三个前面要加UNSAFE_
componentWillMount
componentWillUpdate
componentWillReceiveProps(props)
但是会废弃的 所以最好新版本不要用这三个

新版本的钩子直接看钩子图 新加的使用场景极其罕见 所以最常用的只有三个 DidMount DidUpdate WillUnmount

新版钩子的用法

新版本的钩子
		//从props哪里获取一个派生的状态
static getDerivedStateFromProps(props,state){
	//返回一个state对象 或者null
	//如果返回state对象 则该组件state等于对象值且值不能修改
	//若state的值在任何时候都取决于props,那么可以使用,但是不是必须
	//因为构造器里也可以收到
	return null
	return {props}
	return {name:"你好"}
}
//但是此组件会让代码难以维护等等 所以使用场景极其罕见



这个函数在更新前做点事情 比如拿到更新前节点的滚动条数据
getSnapshotBeforeUpdate(){
	return 快照值 返回值会传给componentDidUpdate(preProps,preState,v)用第三个参数接
}

React应用(通过脚手架)

基本使用

  1. 全剧安装npm i -g create-react-app
  2. 切换到你想创建项目的目录 运行create-react-app hello-react
  3. 进入cd hello-react
  4. 启动项目 npm start yarn start 开启开发者服务器 没有安装yarn也可以npm start yarn build 生成最终的静态文件 yarn eject暴露所有的webpack配置文件(此操作不可逆)

文件解读

public
	index.html 主页面
	manifest.json 应用加壳配置
	robots.txt 爬虫规则文件
src
	App.css
	App.js
	App.test.js 测试文件
	index.js 入口文件
	reportWebVitals.js 测试页面性能配置
	setupTests.js 组件测试

index.html 解读

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- %PUBLIC_URL% 代表public文件夹的路径 直接写相对路径也没关系 -->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想视口 移动端适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 配置浏览器页签+地址栏的颜色(仅支持于安卓手机浏览器并且兼容不太好) -->
    <meta name="theme-color" content="#000000" />
    <!-- 搜索引擎优化 -->
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!-- 苹果浏览器添加到主页面的logo -->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!-- 与应用加壳相关的配置 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <!-- 当浏览器不支持js脚本的运行则显示 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <!-- 容器 -->
    <div id="root"></div>
  </body>
</html>

index.js解读

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App'; //组件
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>	//加了这个组件有错误会提示
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals(); //记录页面性能

样式冲突

可以使用模块化

hello.js
hello.css

把hello.css改名为hello.module.css
然后hello.js里引入
import hello from './hello.module.css'

<div className={hello.你的样式名}></div>

也可以写样式用less

hello{
	.balbala{
	
	}
}

插件的安装 小技巧

安装vs的插件ES7 React/Redux/GraphQL/React-Native snippets 初始化直接rcc

小案例-组件的组合使用

输入框 列表 全选删除区域

拆分为输入框一个组件 列表一个组件 列表里的每个item是一个组件 全选删除区域是一个组件

子组件给父组件传值

父组件给子组件属性绑定一个函数
export default class Father extends Component{
  render(){
    return(
      <div>
        <Son handle={this.handle} />
      </div>
    )
  }
  handle = (data) => {
  	log(data)
  }
}



子组件
直接调用this.props.hanlde(传值父组件就能拿到)

插件生成唯一的key

插件名nanoid比uuid小 npm i nanoid使用时引入

import {nanoid} from 'nanoid'

log(nanoid())

todoList案例总结

  1. 拆分组件,实现静态组件,注意className,style的写法
  2. 动态初始化列表,如何确定将数据放在那个组件的state中? --某个组件使用:放在自身state中 --某些组件使用:放在他们的父组件state中(官方称为:状态提升)
  3. 关于父子组件通信 --父组件给子组件 通过props传递 --子组件给父组件 通过props传递,但是需要父组件提前给子组件传递函数
  4. 注意defaultCheckd和checkd的区别,类似的还有value和defaultValue
  5. 状态在哪里 操作状态的方法就在哪里

React ajax发送请求

axios和node简单复习

node

const { response } = require('express');
const express = require('express')
const app = express()

app.use((request,response,next)=>{
  console.log('有人请求服务器1了');
  next()
})

app.get('/students',(request,response)=>{
  const students = [
    {id:'001',name:'tom',age:18},
    {id:'002',name:'jerry',age:19},
    {id:'003',name:'tony',age:20},
  ]
  response.send(students)
})

app.listen(5000,(err)=>{
  if(!err){console.log('启动成功 可以请求学生数据!');}
})

axios

第一种
  btn = async () => {
    const result = await axios.get('http://localhost:5000/students')
      .catch((err) => {console.log(err);})
  }
  
  
第二种
    $axios.get('http://localhost:5000/students')
    .then((response) => {
      
    },(err) => {
      
    })
 .then里面两个函数第一个成功

react跨域

代理跨域 你3000发给5000可以但是回来数据的时候会被ajax拦截,代理没有ajax阻塞 你自己3000端口 ----- 代理端口3000 ----- 服务器5000

收手机你找老师他不给你,你找家长找老师拿手机,家长在给你 家长就是代理 ,你就是当前地址,老师就是请求地址

假设你是3000 老师是5000

1. 在package.json添加一句
"proxy":"http://localhost:5000" //地址+端口

2. 重启项目

3. 给自己的端口发请求

注意 自己没有的地址才会转发 而不是所有都转发 比如脚手架帮你开了3000 你代理的5000,你请求3000/index.html其实请求的是本地public文件夹下的index.html 如果要往多个地址发请求比如有个5001呢

删掉之前package.json里面的"proxy":"http://localhost:5000“
在src添加新文件setupProxy.js

//引入一个内置的模块 react初始化时就有这个库了
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
  app.use(
    proxy('/api1',{ //遇见/api1前缀的,就会触发代理配置
      target:'http://localhost:5000', //请求转发给谁
      changeOrigin:true, //默认为false,控制服务器收到的请求头中Host字段的值当true值时后台的request.get('Host')为5000,值为false时为3000
      pathRewrite:{'^/api1':''} //重写请求路径 如果不写后台request.url取到是带/api的
    }),
    proxy('/api2',{
      target:'http://localhost:5001',
      changeOrigin:true,
      pathRewrite:{'^/api2':''} //检测到api2就往5001发
    })
  )
}

发请求的时候带api1或者2 会自动转换
$axios.get('http://localhost:3000/api1/students')
$axios.get('http://localhost:3000/api2/cars')

react跨域方法总结

方法一

在package.json中追加如下配置

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

说明:

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

方法二

  1. 第一步:创建代理配置文件
src下创建配置文件:src/setupProxy.js
  1. 编写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. 缺点:配置繁琐,前端请求资源时必须加前缀。

小案例-github搜索

jsx中只可以写表达式 比如三元 不能写语句 比如if

写方法的时候先在App父组件定义好我参数要拿到什么


我要从搜索组件拿这么多东西
  state = {
    users: [],//用户列表
    isFirst: true,//是否为第一次打开页面
    isLoading: false,//请求等待中
    err: '',//存储请求错误信息
  }
我需要写这么多方法放进去吗?

直接这样
  //更新app的状态
  updateAppState = (stateObj) => {
    this.setState(stateObj)
  }
setState的写法要注意 直接让参数传回来一个对象

jsx中只可以写表达式 比如三元 不能写语句 比如if

判断时只能写表达式 三元签套
  render() {
    const { users,isFirst,isLoading,err } = this.props
    return (
      <div className="row">
        {
          isFirst ? <h2>是否第一次使用? 欢迎使用</h2> :
          isLoading ? <h2>数据是否请求完毕? Loading…………</h2> :
          err ? <h2>是否出错?<h2/> :
          users.map((userItem, i, arr) => {
            return <Item userItem={userItem} key={userItem.id} />
          })
        }
      </div>
    )
  }

当请求回来时 存错误对象的时候

    axios.get(`https://api.github.com/search/users1`, {
      params: { q: value }
    }).then((result) => {
      this.props.updateAppState({
        isLoading: false,
        users: result.data.items
      })
    },(err) => {
      console.log(typeof err);
      this.props.updateAppState({
        isLoading: false,
        err:err    //!!!!!这样是不行的因为他是个对象到时候渲染不了对象 要存err身上的message
        err:err.message
      })
    })

任意组件传值-消息订阅-发布机制

PubSubJS

下载包
npm i pubsub-js
或者yarn add pubsub-js



订阅消息 谁接消息谁就订阅消息
var mySubscriber = function(msg,data){
	log(msg,data)
}
var token = PubSub.subscribe('MY TOPIC',mySubscriber) //订阅

或者
var token = PubSub.subscribe('getMainValue', (msg, data) =>{
    console.log('data',data);
})

PubSub.unsubscribe(token) //清除订阅


·······························································


发布消息  //组件一放到页面 就订阅 所以放在componentDidMount中而不是update中
PubSub.publish('MY TOPIC','hello word!') 这里的helloword会传到上面的data里面

token写到this上,如:

  //组件一放到页面 就订阅
  componentDidMount() {
    this.token = PubSub.subscribe('getMainValue', (msg, data) => {
      this.setState(data)
    })
  }
  //卸载
  componentWillUnmount(){
    PubSub.unsubscribe(this.token)
  }

fetch发送请求

有几种发送请求的方式呢?

  1. xhr //window上面的XMLHttpRequest
  2. jQuery//劣势回调地狱
  3. axios//promise风格没有回调地狱 前端jq和axios都是封装的xhr 后端axios封装的http jq,axios都是第三方库,window上带一个fetch不是第三方库而且这哥们也是promise风格的

基本用法对比



    //input框的数据可以通过下面链接搜索github用户
    const { inputRef: { value } } = this
    
    axios.get(`https://api.github.com/search/users`, {
      params: { q: value }
    }).then(
    	result => {},
        err => {}
    )

----------------------------------------------------------------

  
    const { inputRef: { value } } = this

	//简写
    fetch(`https://api.github.com/search/users?q=${value}`).then(
      res => {
        return res.json()	//数据必须在then这个才能拿到
      },
      err => {
      	return new Promise(()=>{})
      }
    ).then(
          res => {
            console.log('获取成功', res);
          },
          err => {
            console.log('获取失败', err.message);
          }
      )


	//简写统一处理错误
    fetch(`https://api.github.com/search/users?q=${value}`).then(
      res => res.json()
    ).then(
      res => { console.log('获取数据成功', res) }
	).catch( //统一处理错误
	  err => {console.log(err.message)}
	)
	
	//简写async await写法
	try{
        const response = await fetch(`https://api.github.com/search/users?q=${value}`)
        const data = await response.json() //await后面只能跟promise,但是response.json()它真就是个promise
        console.log(data)
	}catch(err){ //await不捕获错误所以这里捕获
		console.log(err.message)
	}
	

    //关注分离
    fetch(`https://api.github.com/search/users?q=${value}`).then(
      //仅用于查看是否能连接 就算404也会走成功函数
      res => {
        // console.log('联系服务器成功,但是没有数据', res);
        // 拿数据要在res.json()中 res.json()返回一个promise
        // console.log('res.json()',res.json());
        // 直接then()太乱了 所以return出去在then
        return res.json() //所以这里返回的是promise实例对象  res.json()返回一个promise
      },
      //不可抗拒因素比如断网 会走失败 404不会走 因为404代表没资源但连接上了
      err => { 
      	console.log('联系服务器失败', err);
      	//返回promise则下面的then会执行 返回非promise则会直接成功并输出值
      	//所以如果不写return 失败会默认返回underined 下面也会拿到
      	//返回一个初始化的promise实例 就不会往下走了
      	return new Promise(()=>{})
      }
    ).then( //前面res.json()返回promise所以拿then在接住
          res => {
            // console.log('获取成功', res);
          },
          err => {
            // console.log('获取失败', err.message);
          }
      )

React路由

对路由的理解

一个路由就是一个映射关系key:value 比如/home不是链接时路径path

  1. 后端路由 value时function用来处理客户端提交的请求 注册路由router.get( path,(request,response) => { } ) 当node收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求并返回响应数据
  2. 前端路由 浏览器端value是component,用于展示页面内容 当浏览器的path变为/home时,当前路由组件就会变为Test组件 两种模式 一种H5推出的history模式 BrowserRouter 一种监控hash(锚点)值change事件 HashRouter

基本使用react-router-dom Navlink

react-router
是react的一个插件库,用来实现一个spa应用
基于react的项目基本都会用到此库

但是不能直接引入 有三种用途
web				react-router-dom
native
any(那都能用)




react-router-dom使用分别暴露
两种模式BrowserRouter/HashRouter
import { BrowserRouter, Link, Route } from 'react-router-dom';


//在外面包<BrowserRouter>是错误的写法
//编写路由链接
<BrowserRouter>
    	//路由链接							//to这里最好写小写
    <Link to="/about">About</Link>
    <Link to="/home">Home</Link>
</BrowserRouter>

//在外面包<BrowserRouter>是错误的写法
//注册路由
<BrowserRouter>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
</BrowserRouter>



//在外面包<BrowserRouter>是错误的写法,因为一个<BrowserRouter>标签只管一个
//如果写两个则是两个独立的 不会在一起
//所以整个都要<BrowserRouter>标签包裹住路由链接和注册路由
所以组件里不需要引入BrowserRouter只需要LinkRoute
把<BrowserRouter>标签放到index.js主文件里
ReactDOM.render(<BrowserRouter><App /></BrowserRouter>, document.getElementById('root'));


总结,路由链接自动追加样式

//总结
1.明确界面中的导航区,展示区
2.导航区的链接改为Link
	<Link to="/xxxx">xxxx</Link>
3.展示区写路径的匹配
	<Route path="/xxxx" component={xxxx} />
4.主文件<App/>的最外侧包裹了<BrowserRouter><HashRouter>
5.如果要加样式的话 不使用Link 使用Navlink点击时默认追加active样式 或者使用activeClassName属性来定义
	<NavLink activeClassName="active" to="/about">About</NavLink>

路由组件和普通组件

  1. 位置不同 一般组件在component中 路由组件在pages文件夹下 2.写法不同 一般 路由组件
  2. Props区别 基本组件不给它传数据 那么它的props啥也没有 路由组件会带有固定的三个参数
history:
    go: ƒ go(n)
    goBack: ƒ goBack()
    goForward: ƒ goForward()
    push: ƒ push(path, state)
    replace: ƒ replace(path, state)
history:
    pathname: "/about"
    search: ""
    state: undefined
history:
    params: {}
    path: "/about"
    url: "/about"

封装路由链接Navlink 标签体内容是一个特殊的标签属性children

<NavLink activeClassName="active" className="item" to="/about">About</NavLink>
<NavLink activeClassName="active" className="item" to="/about">About</NavLink>

这样写冗余的东西太多 activeClassName="active" className="item" 
所以封装自己的组件
<MyNavLink to="/home">只需要写to就好了<MyNavLink/>

所以封装

import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'

export default class MyNavLink extends Component {
  render() {
    return (
      <NavLink activeClassName="active" className="list-group-item" {...this.props}>
        {/*this.props.children*/}
      </NavLink>
    )
  }
}




在别的地方写路由链接只需要
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/Home">Home</MyNavLink>
标签里的值也会被当作属性来传递给组件的props传过去的key为children

路由Swich

  1. 通常情况下path和component是一一对应关系
  2. switch可以提高路由匹配效率(单一匹配)
<div>
    <MyNavLink to='/home' children='home' />
    <MyNavLink to='/about' children='about' />
    <MyNavLink to='/test' children='test' />


    <Route path='/home' component={Home} />
    <Route path='/about' component={About} />
    <Route path='/about' component={Test} />
    //这里两个注册路由都会渲染出来 因为找到/about还会往下找
    //所以从路由引入Switch标签 包裹注册路由 就不会往下找了
</div>


import { Route,Switch } from 'react-router-dom';

<Switch>
    <Route path='/home' component={Home} />
    <Route path='/about' component={About} />
    <Route path='/about' component={Test} />
</Switch>

样式丢失问题

脚手架启动 会用webpack的devServer开启一个本地网址 本地网址就指向public文件夹 默认会取index.html 如果你修改让他指向不存在的路径 那么就会默认返回index.html

当路由为多级时 并且引入了public里的资源 刷新网页就会丢失 会在资源路径加上你第一级的路径
因为如果你修改让他指向不存在的路径 那么就会默认返回index.html


<MyNavLink to='/first/home' children='home' />
<MyNavLink to='/first/about' children='about' />

<Route path='/first/home' component={Home} />
<Route path='/first/about' component={About} />
如果是多级/first/home 而且主文件index.html引入了public中的css
<link rel="stylesheet" href="./bootstrap.css">
网页刷新,请求的时候就会带上/first路径

解决方法

第一种方法
					//把点去掉 则逻辑为localhost:3000/bootstrap.css
					//而非./的当前路径下的谁谁
<link rel="stylesheet" href="/bootstrap.css">

第二种
					//public的绝对路径 只适用于react脚手架
<link rel="stylesheet" href="%PUBLIC_URL%/bootstrap.css">


第三中
使用hash路由

模糊匹配与严格模式

一般不要开严格匹配 除非路由转跳不太对 有时候开启会导致无法继续匹配二级路由

匹配不上
<MyNavLink to='/home' children='home' />
<Route path='/a/b/home' component={Home} />

匹配不上
<MyNavLink to='/a/home' children='home' />
<Route path='/home' component={Home} />

匹配的上 模糊匹配
<MyNavLink to='/home/a/b' children='home' />
<Route path='/home' component={Home} />

//注册路由时exact={true}则开启精准匹配 必须一样
<MyNavLink to='/home' children='home' />
<Route exact path='/a/b/home' component={Home} />
<Route exact={true} path='/a/b/home' component={Home} />

路由重定向Redirect

一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

<div>
    <MyNavLink to='/home' children='home' />
    <MyNavLink to='/about' children='about' />

    <Switch>
    <Route path='/home' component={Home} />
    <Route path='/about' component={About} />
    <Redirect to='/about' />
    </Switch>
</div>

匹配不到的时候就找Redirect里面的 Redirect来兜底

嵌套路由

注册子路由时要写上父亲路由的path值 路由的匹配是按照注册路由的顺序进行的 先在父组件里匹配路由

结构
	About
		无
	Home
		News
		Message
	Home里代码为
        <div>
          <MyNavLink to='/Home/News'>News</MyNavLink>
          <MyNavLink to='/Home/Message'>Message</MyNavLink>
        </div>

        <div className='two'>
          <Route path='/Home/News' component={News}></Route>
          <Route path='/Home/Message' component={Message}></Route>
          <Redirect to='/Home/News'></Redirect>
        </div>

路由组件传递params参数

路由链接(携带参数)
<Link to={`/home/message/detail/参数1/参数2}>
    
注册路由(声明接收)
<Route path='/home/message/detail/:参数1的key/:参数2的key' component={xxx}></Route>
    
接收参数 this.props.match.params

代码

export default class Message extends Component {
  state = {
    MessageList: [
      { id: '01', title: '消息1' },
      { id: '02', title: '消息2' },
      { id: '03', title: '消息3' },
    ]
  }
  render() {
    return (
      <div>
        {
          this.state.MessageList.map((v, i, arr) => {
            return (
              <li key={v.id}>
                {/*向路由组件传递params参数 这里得包一个{}因为模板字符串是js里的东西*/}
                <Link to={`/home/message/detail/${v.id}/${v.title}`}>{v.title}</Link>
              </li>
            )
          })
        }
        <hr />
                  {/*声明接受参数 params的key*/}
        <Route path='/home/message/detail/:id/:title' component={Detail}></Route>
      </div>
    )
  }
}



子组件path='/home/message/detail/:id/:title'接受参数在props里



const DetailData = [
  { id: '01', content: '中国' },
  { id: '02', content: '你好' },
  { id: '03', content: '自己' },
]

export default class Detail extends Component {
  render() {
    const { id,title } = this.props.match.params //这里接收params参数
    const {content} = DetailData.find((v) => {
      return v.id === id
    })
    return (
      <ul>
        <li>{id}</li>
        <li>{title}</li>
        <li>{content}</li>
      </ul>
    )
  }
}


路由组件传递search参数

路由链接(携带参数)
<Link to={`/home/message/detail/?id=${v.id}&title=${v.title}`}>{v.title}</Link>
    
注册路由(声明接收)无需接收
    
接收参数 this.props.location.search可以拿到这样的urlencoded编码
?id=01&title=消息1
react自动帮你会下载一个库 名为querystring简写qs
两个方法
qs.parse(xxx)
qs.stringify(xxx)
所以接收参数为 //因为会带有问号所以sclice截断第一位
qs.parse(this.props.location.search.slice(1));

路由组件传递state参数 和状态无关

刷新也可以保留参数

路由链接(携带参数) 写成对象形式
<Link to={{ pathname: '/home/message/detail', state: { id: v.id, title: v.title } }}>{v.title}</Link>

注册路由(声明接收)无需接收 正常注册组件


接收参数
 = this.props.location.state || undefined

push模式和replace模式

一个替换一个压栈

路由的记录是一个一个往里放的先进去的在最里面


replace模式为替换顶部的
两种开启方式
<Link replace to='/xxxx'></Link>
<Link replace={true} to='/xxxx'></Link>

编程式路由导航

基本组件不给它传数据 那么它的props啥也没有 路由组件会带有固定的三个参数 history location match 在history里有两个放法push和replace

params参数
<button onClick={() => this.pushShow(v.id, v.title)}>push查看</button>
<button onClick={() => this.replaceShow(v.id, v.title)}>replace查看</button>

pushShow = (id, title) => {
	//params参数
	this.props.history.push(`/home/message/detail/${id}/${title}`)
	//search参数
    this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
    //state参数
    this.props.history.push(`/home/message/detail`,{id:id,title:title})
}
replaceShow = (id, title) => {
	this.props.history.replace(`/home/message/detail/${id}/${title}`)
}

history也有前进后退方法

<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>

back = (params) => {
	this.props.history.goBack()
}
forward = (params) => {
	this.props.history.goForward()
}
forward = (params) => {
	this.props.history.go(2) //前进几位填几位 负数回退
}

withRouter

一般组件没有history身上的api 用withRouter加工为路由组件

import {withRouter } from 'react-router-dom';

class App extends Component {
  render() {
    return (
      <div>
      	<button onClick={this.back}>回退</button>
      </div>
    )
  }
  back = (params) => {
    this.props.history.goBack()
  }
}

export default withRouter(App) //加工为路由组件 返回一个新组件

history和hash区别

  1. 底层原理不一样 BrowserRouter使用的是H5的history API,不兼容ie9及以下版本 HashRouter使用的是URL的哈希值
  2. hash模式的地址有#
  3. 页面刷新后BrowserRouter的state不会丢失,因为存在history对象中 ,hash会丢失state

项目打包运行

npm run build

但是打包好的是放在服务器上才可以运行
在package.js里加入
"homepage": "."
即可解决


目前如果能正常打开的话那很好,但是可能会出现说就算配置了homepage最后还是显示空白的情况。这时候就要检查你是否使用的是 BrowserRouter (同vue的history模式)需要后端配置支持,否则请使用HashRouter 即带 #。





-------------------------------

或者使用一个库 serve


npm i serve -g


然后在路径下直接serve
如果想去当前下的a文件夹可以直接cd过去
也可以 serve a
serve ./a

扩展知识

setState的回调函数类似nextTick()

对象式

//回调在状态更新后,界面也更新后(render更新后)才被调用
this.setState( {} , callback )


export default class About extends Component {
  render() {
    return (
      <div>
        <h3>我是About的内容</h3>
        <h1>当前求和为{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    )
  }
  state = {
    count: 0
  }
  add = () => {
    const { count } = this.state

    this.setState({ //会开一个异步线程帮你改
      count:count+1
    },() => {  //回调在状态更新后,界面也更新后(render更新后)才被调用
      console.log('回调',this.state);
    })

    console.log('正常',this.state); //这时候react还没给你改好呢 是异步线程
  }
}


点击按钮后输出
正常 {count: 0}
回调 {count: 1}

函数式

	
	this.setState( (state,props)=>{ return } , callback )



    this.setState((state,props) => {
      return {count:state.count+1}
    },() => {
      // 回调函数
    })
    
    this.setState((state,props) => ({count:state.count+1}) , [callback] )

对象其实就是函数的语法糖

lazyload懒加载 Suspense

//引入lazy函数
import React, { Component, lazy } from 'react';
import { Route, Switch, withRouter } from 'react-router-dom';
//lazy包住import组件
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))

class App extends Component {
  render() {
    return (
      <div>

        <MyNavLink to='/home' children='home页'></MyNavLink>
        <MyNavLink to='/about'>about页</MyNavLink>

        <Switch>
          <Route path='/home' component={Home}></Route>
          <Route path='/about' component={About}></Route>
        </Switch>
      </div>
    )
  }
}
export default withRouter(App)



这样报错 因为如果网速慢就一直等么 必须设置一个加载时的组件Suspense
//引入Suspense
import React, { Component, lazy ,Suspense } from 'react';

//Suspense标签 包裹住路由占位符
//Suspense标签的fallback属性为等待时的组件 而且不能懒加载
<Suspense fallback={<h1>Loading</h1>}>
    <Switch>
        <Route path='/home' component={Home}></Route>
        <Route path='/about/:abcdd' component={About}></Route>
    </Switch>
</Suspense>





			//最终为
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))

class App extends Component {
  render() {
    return (
      <div>

        <NavLink to='/home'>home页</NavLink>
        <NavLink to='/about'>about页</NavLink>

        <Suspense fallback={<h1>Loading</h1>}>
          <Switch>
            <Route path='/home' component={Home}></Route>
            <Route path='/about' component={About}></Route>
          </Switch>
        </Suspense>
      </div>
    )
  }
}

Hooks

React16.8的新特性/新语法 让你在函数组件中使用state以及其他的react特性

首先看看差异


import React from 'react'
//函数式
function Fun1() {
  function btn(){
    console.log('btn');
  }
  return (
    <div>
      <h1>数字{}</h1> 没有state无法使用
      <button onClick={btn}>点我+1</button>
    </div>
  )
}


//类式组件
class Fun2 extends React.Component {
  render() {
    return (
      <div>
        <h1>数字{this.state.num}</h1>
        <button onClick={this.btn}>点我+1</button>
      </div>
    )
  }
  state = {
    num: 0
  }
  btn = () => {
    this.setState(state => ({ num: state.num + 1 }))
  }
}

export default Fun1

State Hooks

让函数组件也有状态并进行读写



在函数式组件里使用React.useState(value)
会返回2位数组 在解构这个数组 得到两个参数
第一个为你传入的value  第二个为操作值的方法



//函数式
function Fun1() {

  const [num,setNum] = React.useState(0)

  function btn(){
    setNum(num+1)//第一种写法
    setNum((num) => { //第二种(num参数为之前的值)
      return num+1 //之后的值
    })
    setNum(num => num + 1)//第二种的简写
  }
  return (
    <div>
      <h1>数字{num}</h1>
      <button onClick={btn}>点我+1</button>
    </div>
  )
}

每一次值变化都会调用这个函数 1+n次 随着你的点击
但是每次都React.useState(0)的话那值永远为0
react底层做了处理 不会因为再次调用而覆盖

Effect Hooks生命周期钩子

直接在函数组件函数里写

React.useEffect(() => {
    log(相当于render)
})


React.useEffect(() => {
    log(相当于didmount)
},[])


React.useEffect(() => {
    log(数组里值改变就执行)类似updata
},[num,name])


React.useEffect(() => {
    let timer = setInterval(()={
    	xxxxxxxx
    },1000)
    return ()=>{ clearInterval(timer) } //这个被返回的 为willUnmount即将卸载
},[])

Ref Hooks

和createRef()类似

//函数式
function Fun1(props) {


  const myRef = React.useRef()


  React.useEffect(() => {

  })

  function show(){
    console.log(myRef.current.value);
  }

  return (
    <div>
      <input type="text" ref={myRef} />
      <button onClick={show}>按钮</button>
    </div>
  )
}

扩展api

Fragment

当一个标签 也可以写空标签 只能接收一个key属性 别的不可以

import {Fragment} from 'react'

class xxx extends React.Component {
  render() {
    return (
      <Fragment key={1}>
        <h1>数字{this.state.num}</h1>
        <button onClick={this.btn}>点我+1</button>
      </Fragment>
    )
}

class xxx extends React.Component {
  render() {
    return (
      <>
        <h1>数字{this.state.num}</h1>
        <button onClick={this.btn}>点我+1</button>
      </>
    )
}

Contex组件通信方式

实战一般不用只用用它封装的第三方库 祖组件和后代组件的通信

1. 创建Context容器对象
	在外部创建
	const XxxContext = React.createContext()
2. 渲染组件时,外面包裹XxxContext.Provider,通过value属性给后代组件传递数据
	<xxxContext.Provider value={数据}>
		子组件
	</xxxContext.Provider value={数据}>
3. 后代读取数据
	子组件的子组件也可以直接这样拿
	第一种:仅适用于类组件
	static contextType = xxxContext //声明接收
	this.context //读取context中的value数据
	第二种
	<xxxContext.Consumer>
		{
			value => { //value就是context中的value数据
				要显示的内容
			}
		}
	</xxxContext.Consumer>

儿子组件接收代码

const MyContext = React.createContext()  //创建容器对象

//类式组件
class Fun2 extends React.Component {
  render() {
    return (
      <div>
        <h1>我是祖组件</h1>
        <MyContext.Provider value={this.state.name}> {/*传递 也可以传对象*/}
        <FunFather/>
        </MyContext.Provider>
      </div>
    )
  }
  state = {
    name:'你好'
  }
}


class FunFather extends React.Component {
  render() {
    console.log(this.context); //你好
    return (
      <div>
        <h3>我是父组件</h3>
        <FunSon></FunSon>
      </div>
    )
  }
  static contextType = MyContext //声明
}

//类式儿组件接收代码
class FunSon extends React.Component {
  render() {
    return (
      <h3>我是儿子组件{this.context}</h3>
    )
  }
  static contextType = MyContext //声明
}

//函数儿组件接收代码
function FunSon() {
  return (
    <h3>
      我是儿子组件
      <MyContext.Consumer>
        {
          (value) => { return value }
        }
      </MyContext.Consumer>
    </h3>
  )
}

PureComponent组件优化

Component的两个问题

  1. 只要执行setState(),即使不改变状态数据,也会重新render==》效率低
  2. 只当前组件重新render,就会自动重新render子组件,即便子组件没用用到父组件的任何数据==》效率低

效率高的做法 只有当前组件的state或props数据发生改变时才重新render

原因 Component中的shouldComponentUpdate总是返回true

解决

办法1:
	重写shouldComponentUpdate()方法
	比较新旧state或props数据,如果有变化才返回true,如果没有返回false
办法2:
	使用PureComponent
	PureComponent重写了shouldComponentUpdate只有state或props有改变才返回true
	注意:
		只是进行浅比较,如果知识数据对象内部数据变了,返回false
		所以不要直接修改state数据,而是要产生新数据 地址不可以一样

解决方法1 只能取出来一个一个比较state或props里面的值 如果要比对多个值则做不到

			//参数为下一步要变为的props和state
  shouldComponentUpdate(nextProps,nextState){
    if(nextState.car === this.state.car){
      //如果下一步的和当前的一样 那就不render了
      return false
    }
    return true
  }
  
  //简写
  shouldComponentUpdate(nextProps,nextState){
    return !(nextState.car === this.state.car)
  }

解决方法2

import React from 'react'React.Component改为React.PureComponent
class Parent extends React.PureComponent





注意事项//PureComponent底层会判断obj === this.state?

const obj = this.state //指向同一地址
obj.car = '迈巴赫'
this.setState(obj)  //PureComponent底层会判断obj === this.state?
所以这种方式会修改失败 因为obj和this.state的地址是一样的不会render


直接这样改
this.setState({
	car: '迈巴赫'
})

--------------------------------------------------

比如state里有个stus

const {stus} = this.state  //指向同一地址
stus.unshift('小李')
this.setState({stus:stus})
所以这种方式会修改失败 地址是一样的 PureComponent底层会判断一样不会render

这样改
const {suts} = this.state
this.setState({stus:['小李',...stus]})



//还可以使用数组,对象的浅拷贝
如newArr = [...arr]
newObj = JSON.parse(JSON.stringify(obj))

关于 JSON.parse(JSON.stringify(obj)) 实现深拷贝的一些坑
https://segmentfault.com/a/1190000020297508?utm_source=tag-newest

renderProps 插槽

除了传统的父放a,a里放b,也有另一种父子形式

childrenProps B需要A的数组则收不到

class Parent extends React.PureComponent {
  render() {
    return (
      <div>
        <h1>我是父组件</h1>
        <A>    {/*a里面包b,b会作为a的props里的children值*/}
        	<B></B> 
        </A>
      </div>
    )
  }
}

class A extends React.PureComponent {
  render() {
    return (
      <div>
        <h3>我是A子组件</h3>
        {this.props.children} {/*取出props里的children值*/}
      </div>
    )
  }
}
class B extends React.PureComponent { render() { return <h3>我是B子组件</h3> } }

renderProps,向组件内部动态传入带内容的解构(标签)

class Parent extends React.PureComponent {
  render() {
    return (
      <div>
        <h1>我是父组件</h1>
        <A render={() => <B />}></A>  {/*给a传一个回调函数*/}
      </div>
    )
  }
}


class A extends React.PureComponent {
  render() {
    return (
      <div>
        <h3>我是A子组件</h3>
        {this.props.render()} {/*调用传过来值*/}
      </div>
    )
  }
}
class B extends React.PureComponent { render() { return <h3>我是B子组件</h3> } }

回调函数传值

//类式组件
class Parent extends React.PureComponent {
  render() {
    return (
      <div>
        <h1>我是父组件</h1>
        <A render={(name) => <B name={name} />}></A> {/*2.这里参数接收 赋值给B*/}
      </div>
    )
  }
}


class A extends React.PureComponent {
  render() {
    return (
      <div>
        <h3>我是A子组件</h3>
        {this.props.render(this.state.name)}  {/*1.这里传的值*/}
      </div>
    )
  }
  state = { name: 'tom' }
}
class B extends React.PureComponent { render() { console.log(this.props.name); return <h3>我是B子组件</h3> } }







等于先写一个{this.props.render(this.state.name)}来放插口
这里以后想插什么组件就插什么<A render={()=> <Some/>} />

错误边界Error Boundary

只能捕获后代组件生命周期产生的错误 不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误 错误边界应该在容易发生错误组件的父组件里做手脚

//如果子组件出现任何报错 会触发调用并携带错误信息
static getDerivedStateFromError(err){}
//渲染过程中子组件发生错误
compomentDidCatch(){}




//类式组件
class Parent extends React.PureComponent {
  render() {
    return (
      <div>
        <h1>我是父组件</h1>
        {this.state.hasError ? <h2>页面出错</h2> : <A />}
      </div>
    )
  }
  state = {
    hasError: '' //标识子组件是否产生错误
  }
  //如果子组件出现任何报错 会触发调用并携带错误信息
  static getDerivedStateFromError(err) {
    console.log('err', err);
    return { hasError: err }
  }
}


class A extends React.PureComponent {
  render() {
    return (
      <div>
        <h3>我是A子组件</h3>
        {this.state.name.map((item) => { //这是个错误 因为状态啥都没有
          return <h1>item</h1>
        })}
      </div>
    )
  }
}

组件通信方式总结

组件间的关系

  1. 父子
  2. 兄弟(非嵌套组件)
  3. 祖孙(跨级组件)
通信方式
1. props
	children props
	render props
2. 消息订阅发布
	pubs-sub等
3. 集中式管理
	redux,dva等
4. context
	生产者-消费者模式

比较好的搭配方式

父子组件:props
兄弟组件:消息订阅
祖孙(跨级):消息订阅,集中式,context(开发用的少,封装插件用的多)