React 入门

340 阅读8分钟
  1. React入门

    • 安装官方脚手架 ,后边的脚手架更加好用,但由于官方推荐开始先试试

      npm install -g create-react-app 
      
    • 初始化—创建项目

      create-react-app 项目名
      
    • 运行

      npm start
      
  2. JSX:看起来是js和html的混合体,但实际上html也是由js实现

    React设计之初,就是使用JSX来描述UI,react只做逻辑层,reactDom去做渲染实际的dom,如果换到移动端,就用别的渲染库

    import React from 'react';
    import ReactDOM from 'react-dom';
    class App extends Component {
      render() {
        return (
          <div className="App">
            Hello world!
          </div>
        );
      }
    }
    ReactDOM.render(
        <App />,      //
        document.getElementById('root')  //这里的root元素是在index.html中的dom元素
    );
    

  3. 组件定义

    在上边的代码中我们也可以看到class类在react中就是组件 ,然后使用对应的标签

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    ReactDOM.render(
        <App />,
        document.getElementById('root')
    );
    
    import React, { Component } from 'react';
    import './App.css';
    class App extends Component {
      render() {
        return (
          <div className="App">
            Hello world!
          </div>
        );
      }
    }
    export default App;
    
    
  4. state和setState

    • 使用{ }来渲染变量

      class App extends Component {
          render() {
              const name = '你好啊';
              return (
                  <div className="App">
                      <p>{name}</p>
                  </div>
              );
          }
      }
      
    • 但是如果数据需要修改,并且同时页面响应变化,就需要将数据放在state中,并且使用setState来修改数据,super(props)是调用父级的construtor,来初始化各种生命周期啊,this指向等等数据

      class App extends Component {
          constructor(props) {
              super(props);
              this.state = {
                name:'开课吧'
              }
          }
          render() {
              return (
                  <div className="App">
                      <p>{ this.state.name }</p>
                  </div>
              );
          }
      }
      
    • 使用setState来修改数据 ,使用this.state每次传的是一个新对象,这样好处就是可以做时间旅行

      class App extends Component {
          constructor(props) {
              super(props);
              this.state = {
                name:'开课吧'
              };
              setTimeout(()=>{
                this.setState({
                    name:'开课吧真不错'
                })
              })
          }
          render() { 
              return (
                  <div className="App">
                      <p>{ this.state.name }</p>
                  </div>
              );
          }
      }
      
  5. 属性传递 props

     <App title="今天开始React" />, //使用的时候
    
     render() {
            return (
                <div className="App">
                    <p>{this.props.title}</p>
                </div>
            );
        }
    
  6. 条件渲染和循环

    • 条件渲染

      class App extends Component {
          constructor(props) {
              super(props);
              this.state = {
                  showTitle:true
              };
              },2000)
              setTimeout(() => {
                  this.setState({
                      showTitle: false  // 2秒之后改变成隐藏 
                  })
              },2000)
          }
          render() {
              return (
                  <div className="App">          
                      {this.state.showTitle?<p>{this.props.title}</p>:null}
                      <!--其实这相当于v-if和v-else null那块是else的内容 --> 
                      {this.state.showTitle&&<p>{this.props.title}</p>}
                       <!--直接这样写就是v-if功能 --> 
                  </div>
              );
          }
      }
      
    • 循环

       this.state = {
           goodlist:[
               {text:'百万年薪架构师',price:100,id:1},
               {text:'web全栈架构师',price:100,id:2},
               {text:'Python',price:100,id:3}
           ]
       };
       render() {
              return (
                  <div className="App">
                      <ul>
                          {this.state.goodlist.map(good=>{
                              return <li key={good.id}>
                                  <span>{good.text}</span>
                                  <span>¥{good.price}</span>
                              </li>
                          })}
                      </ul>
                  </div>
              );
          }
      
  7. 数据的管理和绑定

    • 数据的绑定

      • 在react中由于强制的单向数据流,数据绑定的input不能像vue中的那样双向数据修改
      <input type="text"
          value={this.state.add}
          onChange={(e)=>{
              this.setState({
                  add:e.target.value //这样就是实现了input的双向绑定
              })
          }}
      />
      {/*由于value绑定了this.state react是强制的单项数据流,那么value就会被this.state数据监控,现在state的值不变value就不变*/}
      {/*当我们进行输入的时候会触发一个onchang事件 ,我们利用这个实现我们的双向绑定*/}
      {/*利用e.target.value 获得我们输入的值,然后进行对state中数据的修改,然后我们的input中value也会自动进行修改*/}
      
      • 实现将input中的数据,添加到上面state的goodlist数据中去
      <button onClick={()=>{this.handleAdd()}}>添加</button>
      
      handleAdd(){
          this.setState({
              goodlist:[...this.state.goodlist,{text:this.state.add,price:20}],
              add:''
          })
      }
      
    • 事件绑定

      上边的实现将input数据添加到goodlist中去的时候,那个onChange写的太繁琐对不对,我们想到把函数拿出去写,但是会报错,是因为this指向问题,因为当使用onChange={this.handleChange}这种写法时,把handleChange这个函数当成参数传递给了当前组件,组件再去调用自己的内部的this,取向值已经变了,因为这个函数的是给别人用的(谁用的这个函数,this指向谁)

      onChange={this.handleChange}
      
      handleChange(e){
       this.setState({
           add:e.target.value 
       })
      }
      

      解决上边的问题有三种方案

      • 在constructor(props) {}的时候

        this.handleChange = this.handleChange.bind(this);  
        // 这个函数任何时候执行,this都是我们当前组件的this
        
      • 利用箭头函数,解决this指向问题

        <input type="text"
            value={this.state.add}
            onChange={(e)=>this.handleChange(e)}  
            />
        //但是如果传参的话 onClick={this.handleClick(i)}直接这样写就直接执行了
        //onClick = {this.handleClick}
        //但是 这个函数 需要携带一个 i 的参数过去
        //所以 就要用一个匿名函数把i 带过去啊。。
        //onClick = {()=> this.handleClick(i)}
        //这样闭包 让 i 对 renderSquare 的i 保持引用
        
        //-----传参数的函数需要两次箭头函数
        
      • 直接在onChange时候使用bind(this),并不推荐

        <input type="text"
            value={this.state.add}
            onChange={(this.handleChange.bind(this)}  //和我们上边你的添加按钮一样
            />
        
      • 定义handleChang的时候直接使用箭头函数

        handleChange=(e)=>{
            this.setState({
                add:e.target.value
            })
        };
        
  8. 函数组件 :

    如果一个组件只根据props进行渲染,没有内部的state,我们完全可以使用函数式组件的形式来实现

    其实认真观察 函数式组件和class 组件的 props 是有区别的函数式props是通过参数传递就没有this

    class的组件是有this的一般都是this.props

    // 函数式组件
    function Title(props) {   // React的函数式组件props就是传递的值props.title也能直接取到
        return <div>
            <h2>{props.title}</h2>
            <hr/>
        </div>
    }
    <Title title={this.props.title} />  //使用的时候和组件一样
    
  9. 组件通信

    通过props进行通信

    import React from 'react'
    
    function Totol({cart}){
        return <span>
            总价:{cart.reduce((sum,a)=>{
                return sum += a.price*a.count
        },0)}
        </span>
    }
    class Cart extends React.Component{
        render() {
            return <div>
                <table border="1">
                    <thead>
                    <tr>
                        <th>商品名</th>
                        <th>价格</th>
                        <th>数量</th>
                        <th>总价</th>
                    </tr>
                    </thead>
                    <tbody>
                        {this.props.data.map(good=>{
                            return<tr key={good.text}>
                                <td>{good.text}</td>
                                <td>{good.price}</td>
                                <td>{good.count}</td>
                                <td>{good.count*good.price}</td>
                            </tr>
                        })}
                        {/*使用组件做一个总价*/}
                        <tr>
                            <td colSpan="4" align="right">
                                <Totol cart={this.props.data} />
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        }
    }
    export default Cart
    
    AddCart(good){
        let cartIndex;
        const cartGood = this.state.cart.find((value,index) => {
            if (good.text === value.text) {
                cartIndex = index;
                return true
            }
        });
        if (cartGood){
            // 我们要知道我们找到的是第几个,因为我们不能直接修改
            let newCart = [...this.state.cart];
            newCart[cartIndex].count+=1;
            this.setState({
                cart:newCart
            })
    
        } else{
            this.setState({
                cart:[...this.state.cart,{
                    text:good.text,
                    price:good.price,
                    count:1
                }]
            })
        }
    }
    <Cart data={this.state.cart}/>
    
  10. 利用react的每一次都需要setState使用一个新的对象{实现类似时间旅行功能

    //新加一个state的cartHistory:[]来记录
    在每次操作都将操作的数据记录,然后点击返回对应步骤的cartHistory
    this.setState({
    	cartHistory:[...this.state.cartHistory,this.state.cart]
    })
    resetCart(i){  //点击的事件处理
        this.setState({
        	cart:this.state.cartHistory[i]
        })
    }
    
  11. 虚拟DOM

    虚拟DOM是在DOM的基础上建立了一个抽象层,对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中。

    在React中,render执行的结果得到的并不是真正的DOM节点,而仅仅是JavaScript对象,称之为虚拟DOM。

    虚拟DOM具有批处理和高效的Diff算法,可以无需担心性能问题而随时“刷新”整个页面,因为虚拟DOM可以确保只对界面上真正变化的部分进行实际的DOM操作。

    • 传统APP:

    • React App:

    • innerHTML:render html字符串 + 重新创建所有的DOM元素
    • 虚拟DOM:render 虚拟DOM + diff + 更新必要的DOM元素
    • 原理:React会在内存中维护一个虚拟DOM树,对这个树进行读或写,实际上是对虚拟DOM进行。当数据变化时,React会自动更新虚拟DOM,然后将新的虚拟DOM和旧的虚拟DOM进行对比,找到变更的部分,得出一个diff,然后将diff放到一个队列里,最终批量更新这些diff到DOM中。
  12. 生命周期

    React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁

    • 初始化

      1. getDefaultProps()

        设置默认的props,也可以用defaultProps设置组件的默认属性
        这个方法只会调用一次,该组件类的所有后续应用,getDefaultPops 将不会再被调用,其返回的对象可以用于设置默认的 props(properties的缩写) 值。
        
      2. getInitialState()

        这个方法的调用有且只有一次,用来初始化每个实例的 state,在这个方法里,可以访问组件的 props。每一个React组件都有自己的 state,其与 props 的区别在于 state只存在组件的内部,props 在所有实例中共享。
        getInitialState 和 getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而 getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。
        
      3. componentWillMount()

        该方法在  首次(也就是只调用一次) 渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。
        
      4. render()

        该方法会创建一个虚拟DOM,用来表示组件的输出。对于一个组件来讲,render方法是唯一一个必需的方法。render方法需要满足下面几点:
        1.只能通过 this.props 和 this.state 访问数据(不能修改)
        2.可以返回 null,false 或者任何React组件
        3.只能出现一个顶级组件,不能返回一组元素
        4.不能改变组件的状态
        5.不能修改DOM的输出
        render方法返回的结果并不是真正的DOM元素,而是一个虚拟的表现,类似于一个DOM tree的结构的对象。react之所以效率高,就是这个原因。
        
      5. componentDidMount()

        组件渲染之后调用,只调用一次
        
    • 更新 :

      此时组件已经渲染好并且用户可以与它进行交互,比如鼠标点击,手指点按,或者其它的一些事件,导致应用状态的改变,你将会看到下面的方法依次被调用

      1. componentWillReceiveProps(nextProps)

        组件的 props 属性可以通过父组件来更改,这时,componentWillReceiveProps 将来被调用。可以在这个方法里更新 state,以触发 render 方法重新渲染组件。
        
      2. shouldComponentUpdate(nextProps, nextState)

        react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候
        该方法是非必须的,并且大多数情况下没有在开发中使用。
        
      3. componentWillUpdata(nextProps, nextState)

        这个方法和 componentWillMount 类似,在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用,注意不要在此方面里再去更新 props 或者 state。
        
      4. render()

        更新之后再次进行组件渲染
        
      5. componentDidUpdate()

        这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM
        
    • 销毁

      1. componentWillUnmount()

        每当React使用完一个组件,这个组件必须从 DOM 中卸载后被销毁,此时 componentWillUnmout 会被执行,完成所有的清理和销毁工作,在 componentDidMount 中添加的任务都需要再该方法中撤销,如创建的定时器或事件监听器。