React基础v1

React,用于构建用户界面的 JavaScript 库,是Facebook公司的开源项目,用于开发单页面应用;

  • 声明式、模块化、组件化的开发模式,通过对虚拟DOM实现数据绑定,最大限度的减少DOM操作;
  • JSXReact的核心组成部分,它是一种HTMLJS的混合模式,使用XML标记的方式声明界面;
  • React库只做逻辑层,数据 --> VDOM
  • react-dom库做渲染层,去渲染真实的DOMVDOM --> DOM
  1. JSXJavaScript XML,一种类似XMLJS扩展语法;
    1. JSX = XML+JSReact通过 React.createElement() 将其转化成虚拟DOM元素;这也是为什么在使用JSX的地方都必须声明 import React from 'react'
    var ele = <h1>Hello JSX</h1>;
    
    1. 它不再是一个字符串,也不是 HTML/XML 的标签,它最终产生一个 JS 对象--VDOM
  2. 解析规则
    1. 遇到 < 开头的代码,以标签的语法解析;HTML同名的标签转为HTML同名元素,其它标签则当作React组件解析;
    2. 遇到 { 开头的代码,以JS语法解析:标签中的JS代码必须用 { } 包含。
  3. JS库
    1. react.jsReact的核心库;
    2. react-dom.js:提供操作DOMReact扩展库;
    3. babel.min.js:最初是个人开发的,用于把 ES6 转为 ES5,后来被Facebook所收购,增加了解析JSX语法代码并转为纯JS语法代码的功能。
  4. babel的作用:把 JSX 代码转译为纯JS代码,所以 <script> 标签的类型必须指定为babel
    <script type="text/javascript" src="react.development.js"></script>
    <script type="text/javascript" src="react-dom.development.js"></script>
    <script type="text/javascript" src="babel.min.js"></script>
    
    • 纯JS的方式
      <script type="text/javascript">
          const msg = 'Hello JSX!'
          const id = 'main'
          //1. 创建虚拟DOM
          const vDom = React.createElement('h1', { id: id }, msg)
          //2. 把虚拟DOM渲染成真实DOM
          ReactDOM.render(vDom, document.getElementById('test'))
      </script>
      
    • babel的方式
      <script type="text/babel">
          //1. 创建虚拟DOM
          const vDom = <h1 id={id}>{msg}</h1>
          //2. 把虚拟DOM渲染成真实DOM
          ReactDOM.render(vDom, document.getElementById('test'));
      </script>
      
  5. 虚拟DOM最终都会被 React 转为真实DOM,这样才会渲染在页面上。

React脚手架

  1. 脚手架的方式搭建React开发环境
    npx create-react-app demo //安装脚手架 -> 创建项目-> 删除脚手架
    
    npm run eject  // 弹射出项目的配置文件,单向操作,不可逆
    
    但是,如果不想弹射webpack配置文件,又想实现webpack配置呢?试试 @craco/craco
    @craco/craco:https://www.jianshu.com/p/7de3d3b15ff3
  2. 创建项目的报错:npm ERR! Unexpected end of JSON input while parsing near...
    1. 由于网络原因,某些依赖包加载失败,设置 npm 镜像为 cnpm
      npm config set registry https://registry.npm.taobao.org
      
    2. 清除 npm 缓存,即删除 npm-cache 目录下的所有文件,重启create-react-app
      C:\Users\Administrator\AppData\Roaming\npm-cache
      
  3. 目录结构
    • 默认入口文件:src/index.js
      import React from 'react' //引入react的核心库
      import ReactDOM from 'react-dom' //引入react-dom.js,提供与DOM相关的功能
      import './index.css' //项目的公共CSS
      import App from './App' //引入App.js组件,也就是项目的根组件
      
    • registerServiceWorker.jsPWA支持,用于加快react的运行项目
    • ReactDOM.render(<App />, document.getElementById('root')):把根组件App渲染到root节点上
    • src/index.test.js:用于测试
    • src/App.css:App组件中使用的css,import './App.css'
  4. 新建组件Home.js
    import React, { Component } from 'react'
    class Home extends Component {
        render() { return <div>Hello Home</div> }
    }
    export default Home; // 暴露Home组件
    
    1. 挂载到根组件 App
      import Home from './components/Home'
      
      render() { return(<div> <Home /> </div>) }
      
    2. render() 返回的JSX语句称为模板,最外层必须由一个根节点包裹,对于多行的JSX节点,则必须使用 return ( ... )
      render()是唯一必要的方法,返回一个React元素。它不负责组件的实际渲染工作,只是返回一个UI描述。具体的渲染工作由React负责。
      注意:render()必须是一个纯函数,里面不能直接调用setState方法去修改state状态,不能执行任何有副作用的操作。
  5. React 组件也可以通过数组的形式返回一组元素节点:
    render() {
        return [  //不要忘记 key
            <li key="A">First item</li>,
            <li key="B">Second item</li>,
        ];
    }
    
  6. 除了返回数组,还可以使用 Fragment,达到聚合子元素的的目的,又不增加额外节点;
    render() {
        return (
            <>
                <ChildA />
                <ChildB />
            </>
        );
    }
    
    • <></> 其实是 <React.Fragment> 的语法糖
      render() {
          return (
              <React.Fragment>
                  <ChildA />
                  <ChildB />
              </React.Fragment>
          );
      }
      
    • <></> 不能接受键值或属性,但 <React.Fragment> 可以
      render() {
          return (
              <dl>
                  {props.items.map(item => (
                      // 没有`key`,将会触发一个key警告
                      <React.Fragment key={item.id}>
                          <dt>{item.title}</dt>
                          <dd>{item.description}</dd>
                      </React.Fragment>
                  ))}
              </dl>
          );
      }
      
  7. 在模板中使用的HTML节点,必须严格遵循HTML的标准格式,如<img />不能写作<img>

类组件与函数式组件

  • 类组件继承自 React.Component,天生拥有状态state,能访问this
    import React from 'react'
    
    export default class Home extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                counter: 0
            }
        }
        render() {
            return <div className="home">Hello Home</div>
        }
    }
    
    VSCode插件:ES7 React/Redux/GraphQL/React-Nativercc 快速创建一个类组件模板。
  • 函数式组件在 React16.8 之前是没有状态的,只是一个默认接收props的函数,内部没有this。但是从 React16.8 开始引入了Hooks,使得函数式组件也能拥有状态state
    import React from 'react'
    export default function Home(props) {
        return <div className="home">Hello Home</div>
    }
    
    • 函数式组件是一种无状态的组件,为了创建纯展示组件,只负责根据传入的props来展示,不涉及state状态的操作;
    • 组件不会被实例化,整体渲染性能得到提升;
    • 内部不能访问this对象,也无法访问生命周期方法;
    • 无状态组件只能访问输入的props,相同的props得到同样的渲染结果,不会有副作用。

绑定数据

class Home extends Component {
    constructor(props) {  // 参数props用于父子组件的数据传递
        super(props);    // 必须先初始化父类Component的构造方法
        this.state = {  // state 是react用于管理数据的对象
            name:'Mack', age:20, color:'red',
            styles: { color:'red', fontSize:'20px' }
        }
    }
    render() { 
        return (<div>
            <h2>{this.state.name} --- {this.state.age}</h2>
        </div>)
    }
}
  1. 模板中的 { ... } 是JS语句的标识,可以数值、JS变量、JS表达式等;
  2. 状态state
    1. 访问:this.state.name
    2. 更新:this.setState({ name: 'Jack' }),会触发React更新DOM
  3. setState(state, cb)
    • state 可以是一个对象,也可以是一个返回对象的函数;该对象会与状态对象合并,实现更新的目的。
      当它是一个函数时,接收两个参数,分别是上一次 stateprops
      this.setState((state, props) => ({
          name: state.name + props.age  // 更新操作
      }), () => {
          console.log(this.state.name)  //更新后的状态
      });
      
      另外,state、props 总能拿到最新的状态,但外部状态state 以及 DOM 还尚未更新(render()还没有执行)
      this.state = { counter: 0 }
      add() {
          // 第一次调用 setState 更新
          this.setState(state => ({ counter: state.counter + 1 }))
          console.log(this.state.counter)  // 0 ->外部状态state尚未更新
          // 第二次调用 setState 更新
          this.setState(state => {
              console.log(state.counter)  // 1  ->拿到了最新state的状态
              return {
                  counter: state.counter + 2   // 1 + 2 = 3
              }
          })
          console.log(this.state.counter)  // 0  ->外部状态state尚未更新
      }
      render() {
          // 此时的 state 已经更新
          console.log(this.state.counter)  // 3
          // ...
      }
      
    • cb 可选,回调函数。在 state 状态改变,且 render() 执行渲染之后 被回调。
  4. 关于 setState() 的同步异步问题
    概述:在React库控制时,setState 是异步的;否则,它就是同步的!
    • React事件监听回调和生命周期钩子中,setState 是异步的,多次调用会合并更新状态,最后执行一次 render() 渲染;
    • React控制的异步函数,如定时器,promise,原生DOM事件,setState 是同步的,state状态立即改变,并执行 render() 渲染,调用几次 setState 就会同步调用几次render()
  5. 节点属性的变更
    1. 使用class选择器时,建议使用 className
        <span className={this.state.color}>{this.state.name}</h2>
    
    1. 对于<label>上的 for 属性,建议使用 htmlFor
        <label htmlFor="uname">用户名</label><input id="uname" />
    
    1. 行内样式:style属性绑定的其实是一个对象
        <div style={{ color:'red' }}></div><div style={this.state.styles}></div>
    
  6. 引入图片
    • ES6的 import 导入本地图片
       import logo from '../asseets/img/logo.png'
       <img src={logo} />
      
    • 使用ES5的 require 语法
       <img src={require('../asseets/img/logo.png')} />
      
    • 引入远程图片与HTML的标准格式一致,直接引入即可。
  7. 循环数组:必须使用 key 属性标识唯一性
    1. 模板中支持直接循环数组
      this.state = { lists:[<h2 key={1}>11</h2>, <h2 key='2'>22</h2>] }
      render() {
          return (<div>{this.state.lists}</div>)
      }
      
    2. 拼接 <li> 标签数组
      this.state = { nums:[11, 22, 33] }
      render() {
          let result = this.state.nums.map((item, index) => { 
              return <li key={index}>{item}</li>
          })
          return <ul> {result} </ul>
      }
      
    3. 直接在模板中使用JS语句
      render() {
          return (<ul>
              {
                  this.state.nums.map((item, index) => { 
                      return <li key={index}>{item}</li>
                  })
              }
          </ul>)
      }
      
  8. React/Vue 都不建议用数组的索引作为 key
    1. diff算法是比较同级之间的不同,以key来进行关联,当数组的索引变化时,如删除第一条数据,那么后面的所有 index 都会改变,key自然也随之改变;所以,index 作为 key 值是不稳定的,这种不稳定性导致diff无法关联起上一次一样的数据,造成性能的浪费;
    2. Key 应该是稳定的,可预测的,且唯一的。
  9. 模板中的注释:{ /* 注释内容 */ }

绑定事件

  1. 事件的方法就是在组件内定义的方法
        onTest() { ... }
        render() { 
            return <div><button onClick={this.onTest}>点击事件</button></div>
        }
    
    注:绑定事件时,不能写作 this.onTest(),这种方式表示直接调用方法。
  2. 事件方法中的 this 指向问题与解决方式
    函数中的 this 指向默认由调用者决定的,模板的 this 是未定义的,所以在绑定的事件方法中,其 this 也指向undefined,它也不能再访问当前组件内的属性和方法。
    • 通过函数的方法 bind() 修改 this 指向;
      // 方式一:在初始化时修改 this 指向
      // 初始化只有一次,推荐这种方式
      constructor() {
          this.onTest = this.onTest.bind(this)
      }
      // 方式二:在绑定事件方法时就修改 this 指向
      // render每次渲染执行都会因为 bind() 而返回一个新的方法,浪费性能
      render() {
          return <button onClick={this.onTest.bind(this)}>点击事件</button>
      }
      
    • 把方法声明为箭头函数,JSX模板的上一级作用域正是 render(),其this指向组件对象;
      // 推荐方式
      onTest = () => {
          // ......
      }
      
    • 绑定事件时,使用箭头函数调用
      render() {
          // 这种方式也会在每次渲染时返回一个新的方法,浪费性能
          return <button onClick={ev => this.onTest(ev)}>点击事件</button>
      }
      
  3. 事件对象event:在绑定事件方法时,方法的最后一个形参默认为事件对象
    • 无实参时
      onTest(event) {
          let btn = event.target;  //获取当前点击的DOM节点
      }
      render() {
          return <button onClick={this.onTest}>点击事件</button>
      }
      
    • 有实参时
      onMove(a, b, event) {
          let btn = event.target;
      }
      render() {
          return <button onClick={this.onMove.bind(this, 1, 2)}>点击事件</button>
      }
      
  4. 双向数据绑定MVVM
    1. react的数据是单向绑定的,即model变化引起view变化,this.setState()更新model
    2. react没有提供双向数据绑定,只能通过表单节点的 onChange 事件,间接实现MVVM
      this.state = { uname: 'Mack' }
      <input type="text" value={this.state.uname} onChange={this.inputChange} />
      inputChange = ev => {
          this.setState({ uname: ev.target.value }) // view变化触发model变化
      }
      
    3. 如果只是使用 value 绑定数据,而没有绑定 onChange 事件,会报错;
    4. 如果只希望绑定数据,而不绑定onChange事件,则使用defaultValue={this.state.uname}

受控组件与非受控组件

  1. 约束性组件和非约束性组件(受控组件与非受控组件):React中提出的概念
  2. 非约束性组件
    1. 通过 defaultValuedefaultChecked 来设置表单的默认值,value值是用户输入的内容;
      <input type="text" defaultValue="123" />
      <input defaultValue={this.state.value} onChange={e => {this.setState({value: e.target.value})}} />
      
    2. 表单的值只会被渲染一次,在后续的渲染时并不起作用。
  3. 约束性组件(受控组件):
    <input value={this.state.value} onChange={e => this.setState({value: e.target.value})} />
    
    1. value属性绑定了一个变化的数据,它由onChange事件的方法负责管理;
    2. 此时的value不再是用户输入的内容,而是onChange事件触发后,由 this.setState() 引发的一次重新渲染;
    3. 不同的是,React会通过操作虚拟DOM,优化受控组件的整个渲染过程。
  4. <input type="file" /> 始终是一个不受控制的组件,因为它的值只能由用户设置!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容