目录
一、概述
React 起源于 Facebook(脸书) 的内部项目,它是一个用于构建用户界面的 javascript 库,Facebook用它来架设公司的Instagram网站,并于2013年5月开源。
React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。认为它可能是将来 Web 开发的主流工具之一。
二、特点
Ø 声明式
你只需要描述UI看起来是什么样式,就跟写HTML一样,React负责渲染UI
Ø 基于组件
组件时React最重要的内容,组件表示页面中的部分内容
Ø 学习一次,随处使用
使用React可以开发Web应用(ReactJs),使用React可以开发移动端(react-native),可以开发VR应用(react 360)
三、react与与传统mvc的关系
React用于构建用户界面的 JavaScript 库,它不是一个完整的MVC框架,最多可以认为是MVC中的V(View),甚至React并不非常认可MVC开发模式;React 构建页面 UI 的库。可以简单地理解为,React 将界面分成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,就成了我们的页面。
四、浏览器扩展与vscode开发扩展安装
五、命令行构建项目
5.1 脚手架
React团队主要推荐使用create-react-app来创建React新的单页应用项目的最佳方式。
React脚手架(create-react-app)意义
Ø 脚手架是官方提供,零配置,无需手动配置繁琐的工具即可使用
Ø 充分利用 Webpack,Babel,ESLint等工具辅助项目开发
Ø 关注业务,而不是工具配置
create-react-app会配置你的开发环境,以便使你能够使用最新的 JavaScript特性,提供良好的开发体验,并为生产环境优化你的应用程序。你需要在你的机器上安装 Node >= 8.10 和 npm >= 5.6。
5.2 创建react项目
# 全局安装
npm install -g create-react-app
# 构建一个my-app的项目
create-react-app my-app 或 npx create-react-app my-appl
安装成功
六、jsx
6.1 概念
由于通过React.createElement()方法创建的React元素有一些问题,代码比较繁琐,结构不直观,无法一眼看出描述的结构,不优雅,开发时写代码很不友好。
React使用 JSX 来替代常规的JavaScript,JSX 可以理解为的JavaScript语法扩展,它里面的标签申明要符合XML规范要求。React不一定非要使用JSX,但它有以下优点:
Ø JSX 执行更快,因为它在编译为JavaScript代码后进行了优化
Ø 它是类型安全的,在编译过程中就能发现错误
Ø 声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率速
Ø jsx语法中一定要有一个顶级元素包裹,否则编译报错,程序不能运行
6.2 语法
6.2.1 插值语法
在jsx语法中,要把JS代码写到{ }中,所有标签必须要闭合。
let num = 100
let bool = false;
// JSX 语法
//变量,部分表达式,三元,函数
var myh1 = (
<div>
{/* 我是注释 */}
{num}
<hr />
{/* 三目运算 */}
{true ? "条件为真" : "条件为假"}
</div>
)
6.2.2 动态绑定属性
<style>
/* class 是es6定义类的关键词,在jsx中不能使用,只能通过 className来调用定义好的样式类 */
button{color:red;}
.btn{color:red;}
</style>
<script type="text/babel">
const title = '你好世界';
const html1 = '<h3>你好世界</h3>'
// JSX 语法
var myh1 = (
<div>
{/* class加样式 */}
<button title={title}>按钮</button>
<button className="btn">按钮</button>
{/*# 动态显示html元素*/}
<div dangerouslySetInnerHTML={{__html: '<a href="">跳转</a>'}}></div>
<div dangerouslySetInnerHTML={{__html: html1}}></div>
</div>
)
ReactDOM.render(myh1,document.querySelector('#app'));
</script>
</body>
6.2.3 数组列表渲染
<script type="text/babel">
let arr = ["张三","李四","王五","赵六"];
let app = document.getElementById('app');
ReactDOM.render(
<div>
{/* jsx中如果是一维数组,直接写上就可以遍历渲染了 */}
{ arr }
</div>,
app
);
</script>
<script type="text/babel">
let nameList = ['张三', '李四', '王五'];
let app = document.getElementById('app');
ReactDOM.render(
<div>
{/* 方案1*/}
{
nameList.map((item,index) => {
return (<h3 key={index}>{item}</h3>)
})
}
{/* 方案2 【推荐】*/}
{
nameList.map((item,index) => ((
<h3 key={index}>{item}</h3>
)))
}
</div>,
app
);
</script>
6.2.4 渲染对象列表
let obj = {
name:'大壮',
age:22,
address:'北京'
}
const mydiv = <ul>
{
Object.keys(obj).map((val,index)=>{
return <li key={ index }>{ val } --- { obj[val] }</li>
})
}
</ul>
ReactDOM.render(mydiv,
document.getElementById('app')
)
七、React事件处理
7.1 事件绑定
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
Ø React 事件的命名采用小驼峰式,而不是纯小写。
onClick onChange
Ø 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
onClick={this.fn} // 注函数或方法不能用小括号
Ø 类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用
类组件:
函数组件:
7.2 传值
正确的写法用匿名函数回调函数进行传值
7.3 事件对象
React中可以通过事件处理函数的参数获取到事件对象,它的事件对象叫做:合成事件,即兼容所有浏览器,无需担心跨浏览器兼容问题,此事件对象还拥有和浏览器原生事件相同的接口,包括 stopPropagation()和 preventDefault(),如果你想获取到原生事件对象,可以通过 e.nativeEvent 属性来进行获取。
作用:获取dom元素
react中的实现没有提供像vue中的事件修饰符,像冒泡和默认行为,需要开发者自行解决。
函数组件:
import React,{ Component } from 'react'
function Cmp(props) {
const fn = (e) =>{
//获取当前dom
console.log(e.target);
//元素中的值
console.log(e.target.innerText);
//获取属性
console.log(e.target.getAttribute('name'));
//取消默认行为
e.preventDefault()
}
return (
<div>
<button name="btn" onClick={fn}>点击事件</button>
<a href="http://www.baidu.com" onClick={fn}>点击事件</a>
</div>
)
}
export default Cmp
合成事件对象
所有事件都挂在到document上
event不是原生的,是syntheticEvent合成事件对象
和vue事件不同和dom事件也不同
react为何要合成事件机制?
更好的兼容性和跨平台
挂在到document,减少内存消耗,避免频繁解绑
方便事件统一管理(如事务机制)
7.4 this.指向问题
在JSX事件函数方法中的 this,默认不会绑定 this指向。如果你忘记绑定,当你调用这个函数的时候 this 的值为 undefined。所以使用时一定要绑定好this的指向。
Ø 构造方法中绑定
import React,{ Component } from 'react'
export default class extends React.Component {
constructor(props){
super(props)
// 在构造方法中指定this指向 <button onClick={this.fun}>按钮</button>
this.clickHandle = this.clickHandle.bind(this)
}
clickHandle(){
console.log(this);
console.log('dianji');
}
render(){
return (
<div><button onClick = {this.clickHandle}>点我点我点我</button></div>
)
}
}
Ø 申明式使用bind绑定+传参
<button onClick={this.fun.bind(this,参数)}>按钮</button>
Ø 箭头函数绑定+传参 【推荐】
<button onClick = {(evt) => this.fun(evt,参数)}>按钮</button>
Ø 定义事件方法使用箭头函数来绑定
// 通过箭头函数定义事件方法,也能解决this指向问题
clickHandle = (val) => {
console.log(this);
console.log('dianji');
console.log(val);
}
render(){
return (
<div><button onClick = {this.clickHandle}>点我点我点我</button></div>
)
}
八、state状态
8.1 基本使用
Ø 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
Ø state的值是对象,表示一个组件中可以有多个数据
Ø 通过this.state来获取状态,react中没有做数据代理
Ø state数据值可以修改 this.setState
Ø state可以定义在类的构造方法中也可以写在类的成员属性
export default class extends React.Component {
constructor(props){
super(props)
// 第一种初始化方式
this.state = {
count : 0
}
}
/*
// 第二种初始化方式
state = {
count:1
}
*/
render(){
return (
<div>计数器 :{this.state.count}</div>
)
}
}
8.2 修改状态
state中的值不能直接通过修改state中的值来进行修改数据操作,react提供一个this.setState方法来完成state数据的修改操作
setState() 作用:
1.修改 state
2.更新UI
setState更新数据源是异步操作,不是同步,异步效率更高(js是单线程)
语法1
this.setState({
key:value
})
语法2
this.setState(state=>{
return{
num:state.num + 1
}
})
8.3 props和state的区别
Ø props 中存储的数据,都是外界传递到组件中的
Ø props 中的数据,都是只读的
Ø state 中的数据,都是可读可写的
Ø props 在函数声明或类申明的组件中都有
Ø state 只有类申明的组件中才有
8.4 关于state的深度思考
(1) setState为什么设计为异步?
- 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
- 最好的办法应该是获取到多个更新,之后进行批量更新
(2) 那么如何可以获取到更新后的值呢?
方式一:setState的回调
方式二:在生命周期函数内获取
方式三:异步中
(3) setState一定是异步吗?
- 在组件生命周期或React合成事件中,setState是异步;
- 在setTimeout或者原生dom事件中,setState是同步;
//1. setState 可能是异步更新(有可能是同步更新)
this.setState({
count: this.state.count + 1
}, () => {
// 联想 Vue $nextTick - DOM
console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值
//2. setTimeout 中 setState 是同步的
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('count in setTimeout', this.state.count)
}, 0)
//3.自己定义的 DOM 事件,setState 是同步的。再 componentDidMount 中
componentDidMount() {
// 自己定义的 DOM 事件,setState 是同步的
document.body.addEventListener('click', this.bodyClickHandler)
}
bodyClickHandler = () => {
this.setState({
count: this.state.count + 1
})
console.log('count in body event', this.state.count)
}
componentWillUnmount() {
// 及时销毁自定义 DOM 事件
document.body.removeEventListener('click', this.bodyClickHandler)
// clearTimeout
}
(4) state可能会被合并如何理解?
// state 异步更新的话,更新前会被合并
// 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
//Object.assign({count:1},{count:1},{count:1})
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
(5) 关于对象和数组如何实现不可变值操作
//不可变值 - 数组
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
})
// 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值
//不可变值 - 对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
总结:
-
setState->有时异步(普通使用),有时同步(setTimout,dom事件)
-
有时合并(对象形式 Object.assign),有时不合并(函数形式)
九、 Props进阶
9.1 children属性
children属性,表示组件标签的子节点,当组件标签有子节点时,props就会有该属性,与与普通的props一样,其值可以使任意类型。单标签和双标签中没有数据都是没有此属性。
# 父组件
class App extends React.Component {
render() {
return (
<div>
<Cmp>我是children中的值</Cmp>
</div>
)
}
}
# 子组件
{props.children} 获取数据
9.2 类型限制(Props-type)
对于组件来说,props是外部传入的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据,如果传入的数据不对,可能会导致程序异常,所以必须要对于props传入的数据类型进行校验。
#安装校验包
npm i -S prop-types
# 在组件中导入
import PropTypes from 'prop-types'
# 函数组件
function App(){}
// 验证规则
App.propTypes = {
prop-name:PropTypes.string
}
# 类组件
class App extends Component{
// 类内部完成 检查
static propTypes = {
prop-name:PropTypes.string
}
}
#约束类型
https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#proptypes
- 类型: array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired
- 特定结构的对象: shape({})
9.3 默认值
如果props没有属性没有传过数据,为了不让程序异常,可以设置其默认值。
# 函数组件
function App(){}
App.defaultProps = {
title: '标题'
}
# 类组件
class App extends Component {
static defaultProps = {
title: '标题'
}
}
十、表单处理
10.1 受控组件
将state与表单项中的value值绑定在一起,有state的值来控制表单元素的值,称为受控组件。
Ø 在state中添加一个状态,作为表单元素的value值
Ø 给表单元素绑定change事件,将表单元素的值设置为state的值
<input type="text" value={this.state.username} onChange={this.inputChange.bind(this)} />
注:多表单元素需优化事件方法
this.setState({
username: e.target.value
})
input textarea select的受控组件案例:
// textarea - 使用 value
return <div>
<textarea value={this.state.info} onChange={this.onTextareaChange}/>
<p>{this.state.info}</p>
</div>
// select - 使用 value
return <div>
<select value={this.state.city} onChange={this.onSelectChange}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
<p>{this.state.city}</p>
</div>
onTextareaChange = (e) => {
this.setState({
info: e.target.value
})
}
onSelectChange = (e) => {
this.setState({
city: e.target.value
})
}
checkbox radio受控组件案例:
// checkbox
return <div>
<input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
<p>{this.state.flag.toString()}</p>
</div>
// radio
return <div>
male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
<p>{this.state.gender}</p>
</div>
onCheckboxChange = () => {
this.setState({
flag: !this.state.flag
})
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
})
}
10.2 非受控组件
没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值
Ø 调用 React.createRef() 方法创建ref对象
Ø 将创建好的 ref 对象添加到文本框中
Ø 通过ref对象获取到文本框的值
import React, { Component } from 'react'
import FromDemo from './02_FormDemo'
/*
ref案例
liujie 2021-10-20
*/
/*
1.创建一个ref this.username = React.createRef()
2.绑定到文本框 ref={ this.username }
3.获取文本框值 this.username.current.value
注意:ref获取dom元素 获取组件实例 等同于vue ref
*/
export default class RefDemo extends Component {
constructor(props){
super(props)
this.username = React.createRef()
this.formcom = React.createRef()
}
handerclick = () => {
console.log(this.username.current);
console.log('用户名:',this.username.current.value);
console.log(this.formcom.current.state.radioValue);
//vue写法 this.refs.myform.value
}
render() {
return (
<div>
用户名:<input type="text" ref={ this.username }/>
<button onClick={ this.handerclick }>登陆</button>
<FromDemo ref={ this.formcom }/>
</div>
)
}
}
场景:
1.必须手动操作dom元素,setSate实现不了
2.文件上传<input type=file>
3.某些富文本编辑器,需要传入dom元素
受控组件Vs非受控组件:
1.优先使用受控组件,符合react设计原则
2.必须操作dom,在使用非受控组件