React
,用于构建用户界面的 JavaScript 库,是Facebook
公司的开源项目,用于开发单页面应用;
- 声明式、模块化、组件化的开发模式,通过对虚拟DOM实现数据绑定,最大限度的减少DOM操作;
-
JSX
是React
的核心组成部分,它是一种HTML
和JS
的混合模式,使用XML
标记的方式声明界面; -
React
库只做逻辑层,数据 --> VDOM
-
react-dom
库做渲染层,去渲染真实的DOM
,VDOM --> DOM
-
JSX
:JavaScript XML
,一种类似XML
的JS
扩展语法;-
JSX = XML+JS
,React
通过React.createElement()
将其转化成虚拟DOM
元素;这也是为什么在使用JSX
的地方都必须声明import React from 'react'
var ele = <h1>Hello JSX</h1>;
- 它不再是一个字符串,也不是
HTML/XML
的标签,它最终产生一个JS
对象--VDOM
。
-
- 解析规则
- 遇到
<
开头的代码,以标签的语法解析;HTML
同名的标签转为HTML
同名元素,其它标签则当作React
组件解析; - 遇到
{
开头的代码,以JS语法解析:标签中的JS代码必须用{ }
包含。
- 遇到
- JS库
-
react.js
:React
的核心库; -
react-dom.js
:提供操作DOM
的React
扩展库; -
babel.min.js
:最初是个人开发的,用于把ES6
转为ES5
,后来被Facebook
所收购,增加了解析JSX
语法代码并转为纯JS
语法代码的功能。
-
-
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>
- 纯JS的方式
- 虚拟
DOM
最终都会被React
转为真实DOM
,这样才会渲染在页面上。
React脚手架
- 脚手架的方式搭建React开发环境
但是,如果不想弹射webpack配置文件,又想实现webpack配置呢?试试npx create-react-app demo //安装脚手架 -> 创建项目-> 删除脚手架 npm run eject // 弹射出项目的配置文件,单向操作,不可逆
@craco/craco
@craco/craco:https://www.jianshu.com/p/7de3d3b15ff3 - 创建项目的报错:
npm ERR! Unexpected end of JSON input while parsing near...
- 由于网络原因,某些依赖包加载失败,设置 npm 镜像为 cnpm
npm config set registry https://registry.npm.taobao.org
- 清除
npm
缓存,即删除npm-cache
目录下的所有文件,重启create-react-app
C:\Users\Administrator\AppData\Roaming\npm-cache
- 由于网络原因,某些依赖包加载失败,设置 npm 镜像为 cnpm
- 目录结构
- 默认入口文件:
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.js
:PWA
支持,用于加快react的运行项目 -
ReactDOM.render(<App />, document.getElementById('root'))
:把根组件App渲染到root节点上 -
src/index.test.js
:用于测试 -
src/App.css
:App组件中使用的css,import './App.css'
- 默认入口文件:
- 新建组件
Home.js
import React, { Component } from 'react' class Home extends Component { render() { return <div>Hello Home</div> } } export default Home; // 暴露Home组件
- 挂载到根组件
App
上import Home from './components/Home' render() { return(<div> <Home /> </div>) }
-
render()
返回的JSX
语句称为模板,最外层必须由一个根节点包裹,对于多行的JSX
节点,则必须使用return ( ... )
render()
是唯一必要的方法,返回一个React
元素。它不负责组件的实际渲染工作,只是返回一个UI描述。具体的渲染工作由React
负责。
注意:render()
必须是一个纯函数,里面不能直接调用setState
方法去修改state
状态,不能执行任何有副作用的操作。
- 挂载到根组件
- React 组件也可以通过数组的形式返回一组元素节点:
render() { return [ //不要忘记 key <li key="A">First item</li>, <li key="B">Second item</li>, ]; }
- 除了返回数组,还可以使用
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> ); }
-
- 在模板中使用的
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-Native
,rcc
快速创建一个类组件模板。 - 函数式组件在
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>)
}
}
- 模板中的
{ ... }
是JS语句的标识,可以数值、JS变量、JS表达式等; - 状态
state
- 访问:
this.state.name
- 更新:
this.setState({ name: 'Jack' })
,会触发React更新DOM
- 访问:
-
setState(state, cb)
-
state
可以是一个对象,也可以是一个返回对象的函数;该对象会与状态对象合并,实现更新的目的。
当它是一个函数时,接收两个参数,分别是上一次state
和props
。
另外,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()
执行渲染之后 被回调。
-
- 关于
setState()
的同步异步问题
概述:在React
库控制时,setState
是异步的;否则,它就是同步的!- 在
React
事件监听回调和生命周期钩子中,setState
是异步的,多次调用会合并更新状态,最后执行一次render()
渲染; - 非
React
控制的异步函数,如定时器,promise
,原生DOM
事件,setState
是同步的,state
状态立即改变,并执行render()
渲染,调用几次setState
就会同步调用几次render()
- 在
- 节点属性的变更
- 使用
class
选择器时,建议使用className
<span className={this.state.color}>{this.state.name}</h2>
- 对于
<label>
上的for
属性,建议使用htmlFor
<label htmlFor="uname">用户名</label><input id="uname" />
- 行内样式:
style
属性绑定的其实是一个对象
<div style={{ color:'red' }}></div><div style={this.state.styles}></div>
- 使用
- 引入图片
- ES6的
import
导入本地图片import logo from '../asseets/img/logo.png' <img src={logo} />
- 使用ES5的
require
语法<img src={require('../asseets/img/logo.png')} />
- 引入远程图片与
HTML
的标准格式一致,直接引入即可。
- ES6的
- 循环数组:必须使用
key
属性标识唯一性- 模板中支持直接循环数组
this.state = { lists:[<h2 key={1}>11</h2>, <h2 key='2'>22</h2>] } render() { return (<div>{this.state.lists}</div>) }
- 拼接
<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> }
- 直接在模板中使用JS语句
render() { return (<ul> { this.state.nums.map((item, index) => { return <li key={index}>{item}</li> }) } </ul>) }
- 模板中支持直接循环数组
-
React/Vue
都不建议用数组的索引作为key
值-
diff
算法是比较同级之间的不同,以key
来进行关联,当数组的索引变化时,如删除第一条数据,那么后面的所有index
都会改变,key
自然也随之改变;所以,index
作为key
值是不稳定的,这种不稳定性导致diff
无法关联起上一次一样的数据,造成性能的浪费; -
Key
应该是稳定的,可预测的,且唯一的。
-
- 模板中的注释:
{ /* 注释内容 */ }
绑定事件
- 事件的方法就是在组件内定义的方法
注:绑定事件时,不能写作onTest() { ... } render() { return <div><button onClick={this.onTest}>点击事件</button></div> }
this.onTest()
,这种方式表示直接调用方法。 - 事件方法中的
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> }
- 通过函数的方法
- 事件对象
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> }
- 无实参时
- 双向数据绑定
MVVM
- react的数据是单向绑定的,即
model
变化引起view
变化,this.setState()
更新model
- 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变化 }
- 如果只是使用
value
绑定数据,而没有绑定onChange
事件,会报错; - 如果只希望绑定数据,而不绑定
onChange
事件,则使用defaultValue={this.state.uname}
- react的数据是单向绑定的,即
受控组件与非受控组件
- 约束性组件和非约束性组件(受控组件与非受控组件):React中提出的概念
- 非约束性组件
- 通过
defaultValue
或defaultChecked
来设置表单的默认值,value
值是用户输入的内容;<input type="text" defaultValue="123" /> <input defaultValue={this.state.value} onChange={e => {this.setState({value: e.target.value})}} />
- 表单的值只会被渲染一次,在后续的渲染时并不起作用。
- 通过
- 约束性组件(受控组件):
<input value={this.state.value} onChange={e => this.setState({value: e.target.value})} />
-
value
属性绑定了一个变化的数据,它由onChange
事件的方法负责管理; - 此时的
value
不再是用户输入的内容,而是onChange
事件触发后,由this.setState()
引发的一次重新渲染; - 不同的是,React会通过操作虚拟DOM,优化受控组件的整个渲染过程。
-
-
<input type="file" />
始终是一个不受控制的组件,因为它的值只能由用户设置!