一、React简介
1.1 React是什么 ?
React用于构建用户界面的JavaScript库,是一个将数据渲染为HTML视图的开源JavaScript库。
1.2 为什么学React?
- 原生JavaScript操作DOM繁琐,效率低(DOM-API操作UI)
- 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
- 原生JavaScript没有组件化编码方案,代码复用率低
1.3 React的特点
- 采用组件化模式、声明式编码,提高开发效率及组件复用率
- 在React Native中可以使用React语法进行移动端开发
- 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
二、React 初体验
2.1 hello_react案例
- js库引入顺序;
- 创建虚拟DOM时不加引号;
- script标签为babel;
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!--引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
<script type="text/babel"> /* 此处写babel,将jsx转为浏览器能够识别的js */
// 1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1> /* 此处不写引号,因为不是字符串 */
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
</body>
2.2 关于虚拟DOM
- 本质时Object 类型的对象(一般对象)
- 虚拟DOM较“轻”,真实DOM较“重”,因为虚拟DOM是在React内部用,无需真实DOM上那么多的属性
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
2.3 jsx语法规则
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时要用{}
- 样式的类名指定不要用class,要用className
- 内联样式,要用style={{key:value}}的形式去写;外侧花括号代表里面要写js表达式,里侧花括号代表的是一个对象
- 只有一个根标签
- 标签必须闭合
- 标签首字母
(1)若小写字母开头,则将该标签专区html中同名元素,若html中无该标签对应的同名元素,则报错
(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
<script type="text/babel">
const myId = 'aYguiGu'
const myData = 'HelLo,ReAct'
// 1.创建虚拟DOM
const VDOM = ( /* 此处不写引号,因为不是字符串 */
<div>
<h1 className="title" id={myId.toLocaleLowerCase()}>
<span style={{ color: 'white', fontSize: '30px' }}>{myData.toLocaleLowerCase()}</span>
</h1>
<h1 className="title" id={myId.toLocaleUpperCase()}>
<span style={{ color: 'white', fontSize: '30px' }}>{myData.toLocaleLowerCase()}</span>
</h1>
<input type="text" />
</div>
)
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
2.4 jsx练习
区分 【js语句代码】与【js表达式】
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值得地方
例如: (1)a (2)a+b (3)demo(1) (4)arr.map() (5)function test() {}- 语句(代码)
例如:(1)if(){} (2)for(){} (3)switch(){case:xxxx}
<script type="text/babel">
const data = ['Angular', 'React', 'Vue']
// 1.创建虚拟DOM
const VDOM = (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
// 2.渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
2.5 模块、组件、模块化、组件化
- 模块:向外提供特定功能的js程序,一般就是一个js文件
作用:复用js,简化js的编写,提高js运行效率- 组件:用来实现局部功能效果的代码和资源的集合
作用::复用编码,简化项目编码,提高运行效率- 模块化:当应用的js都以模块来编写的,这个应用就是一个模块化的应用
- 组件化:当应用是以多组件的方式实现,这个应用就是一个组件化的应用。
三、React面向组件编程
3.1.安装React开发者工具
3.2 基本理解和使用
3.2.1 react中定义函数式组件
<script type="text/babel">
// 1. 创建函数式组件
function MyComponent() {
console.log(this); //此处的this时undefined,因为babel编译后开起来严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
执行了ReactDOM.render(,document.getElementById(‘test’))之后,发生了什么?
- React解析组件标签,找到了MyComponent组件
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
3.2.2 类的基本知识(复习)
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
- 类中所定义的方法,都是放在了类的原型对象上,供实例去使用
<script type="text/javascript">
// 创建一个Person类
class Person {
//构造器方法
constructor(name, age) {
// 构造器中的this是谁?————类的实例对象
this.name = name
this.age = age
}
speak() {
// speak方法放在了哪里?————类的原型对象上,供实例使用
// 通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},我的年龄是${this.age}岁`);
}
}
// 创建一个Student类,继承于Person类
class Student extends Person {
constructor(name, age, grade) {
super(name, age)
this.grade = grade
this.school = '翻斗幼儿园'
}
// 重写从父类继承过来的方法
speak() {
console.log(`我叫${this.name},我的年龄是${this.age}岁,我读的是${this.grade}年级`);
}
study() {
//study方法放在了哪里?————类的原型对象上,供实例使用
// 通过Person实例调用study时,study中的this就是Person实例
console.log('good good study,day day up!');
}
}
const s1 = new Student('胡图图',3,'小一班')
console.log(s1);
s1.speak()
s1.study()
</script>
3.2.3 类式组件
<script type="text/babel">
// 1. 创建类式组件
class MyComponent extends React.Component {
render() {
// render是放在哪里的?————MyComponent的原型对象上,供实例使用
// render中的this是谁?————MyComponent的实例对象 <==> MyComponent组件实例对象
console.log('render中的this', this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
// 2. 渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>
执行了ReactDOM.render(,document.getElementById(‘test’))之后,发生了什么?
- React解析组件标签,找到了MyComponent组件
- 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render’方法
- 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
3.3 state属性
3.3.1 理解
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- 组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
3.3.2 state基本使用
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
// 构造器调用几次?————1次
constructor(props) {
console.log('constructor');
super(props)
// 初始化状态
this.state = { isHot: false,wind:'微风' }
// 解决changeWeather中this指向问题
this.changeWeather0 = this.changeWeather.bind(this)
//bind之后,this由undefined变为==>Weather类组件的实例对象;然后将左侧新的函数放到了实例自身,即this,并取新名字为changeWeather0
}
// render调用几次?————1+n次,1是初始化的那次,n是状态更新的次数
render() {
console.log('render');
// 读取状态
const { isHot,wind } = this.state
return <h1 onClick={ this.changeWeather0 }>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
// changeWeather调用几次?————点几次,调用几次
changeWeather(){
// changeWeather放在哪里?————Weather的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开起来局部的严格模式,所以changeWeather中的this为undefined
console.log('changeWeather');
// 获取原来的isHot值
const isHot = this.state.isHot
// 【注意】:状态必须通过setState进行更新,且更新是一种合并,不是替换
this.setState({isHot:!isHot})
// 状态(state)不可直接更改,下面是错误的写法
// this.state.isHot = !isHot
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
3.3.3 state简写形式
<script type="text/babel">
// 1. 创建类式组件
class Weather extends React.Component {
state = { isHot: false, wind: '微风' }
render() {
// 读取状态
const { isHot, wind } = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
// changeWeather现在是Weather实例对象上的一个属性
// 自定义方法————赋值语句+箭头函数
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
【注意】:
1)组件中render方法中的this为组件实例对象
2)组件自定义的方法中this为undefined,如何解决? a. 强制绑定this:通过函数对象的bind() b. 箭头函数
3)状态数据,不能直接修改或更新
3.4 props属性
3.4.1 理解
- 每个组件对象都会有props(properties的简写)属性
- 组件标签的所有属性都保存在props中
3.4.2 作用
- 通过标签属性从组件外向组件内传递变化的数据
- 注意:组件内部不要修改props数据
3.4.3 组件实例中使用props
<script type="text/babel">
// 创建组件
class Person extends React.Component {
constructor(props){
// 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
super(props)
console.log('constructor',this.props);
}
/*static 静态方法;该形式将propTypes添加到Person身上*/
// 对标签属性进行类型、必要性的限制
static propTypes = {
name: PropTypes.string.isRequired, //限制name必传,且为字符串
sex: PropTypes.string, //限制sex为字符串
age: PropTypes.number, //限制age为数值
}
// 指定默认标签属性值
static defaultProps = {
sex: '男', //sex默认值为男
age: 18, //age默认值为18
}
render() {
console.log(this);
const { name, age, sex } = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Person name="jerry" sex="女" />, document.getElementById('test1'))
</script>
3.4.4 函数组件使用props
<script type="text/babel">
// 创建组件
function Person(props) {
const { name, age, sex } = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
}
Person.defaultProps = {
sex: '男',
age: 18,
}
// 渲染组件到页面
ReactDOM.render(<Person name="jerry" sex="女" />, document.getElementById('test1'))
</script>
3.5 ref属性
3.5.1 字符串形式的ref
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
// 展示左侧输入框的数据
showData = () => {
const {input1} = this.refs
alert(input1.value)
}
// 展示右侧输入框的数据
showData2 = () => {
const {input2} = this.refs
alert(input2.value)
}
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo/>, document.getElementById('test'))
</script>
3.5.2 回调形式的ref
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
// 展示左侧输入框的数据
showData = () => {
const { input1 } = this
alert(input1.value)
}
// 展示右侧输入框的数据
showData2 = () => {
const { input2 } = this
alert(input2.value)
}
render() {
return (
<div>
<input ref={(currentNode) => { this.input1 = currentNode }} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={c => this.input2 = c} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
3.5.3 createRef
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器只能存储一个节点
<script type="text/babel">
// 1. 创建类式组件
class Demo extends React.Component {
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框的数据
showData = () => {
alert(this.myRef.current.value)
}
// 展示右侧输入框的数据
showData2 = () => {
alert(this.myRef2.current.value)
}
render() {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
3.6 高阶函数柯里化
- 高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数
(1)若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
(2)若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常见的高阶函数:Promise、setTimeout、arr.map()等等- 函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
<script type="text/babel">
// 1. 创建类式组件
class Login extends React.Component {
state = {
username: '',
password: ''
}
// 保存数据到表单
saveFormData = (dataType) => {
return (event) => {
this.setState({ [dataType]: event.target.value })
}
}
handleSubmit = (event) => {
event.preventDefault();
const { username, password } = this.state
alert(`用户名:${username},密码:${password}`)
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
密码:<input onChange={this.saveFormData('password')} type="text" name="password" />
<button>登录</button>
</form>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Login />, document.getElementById('test'))
</script>
3.7 生命周期函数
3.7.1 旧版生命周期过程
1. 挂载组件时执行顺序:
构造器(constructor)–> 组件将要挂载(componentWillMount)–> 组件渲染(render)—> 组件挂载完毕(componentDidMount)
页面加载完毕,控制打印如下结果:
2. 组件状态更新执行顺序
组件是否应该更新(shouldComponentUpdate)–> 组件将要更新(componentwillUpdate)–> 组件渲染(render) --> 组件更新完毕(componentDidUpadte)
shouldComponentUpdate默认返回true
页面加载完毕后,点击“点我+1”按钮,控制台打印结果如下:
3. 强制更新执行顺序
组件将要更新(componentwillUpdate)–> 组件渲染(render) --> 组件更新完毕(componentDidUpadte)
forceUpdate()和setState是react的api,不询问是否应该更新(shouldComponentUpdate)
点击红色框内按钮,强制更新组件:
代码如下:
<script type="text/babel">
// 1. 创建类式组件
class Count extends React.Component {
constructor(props) {
console.log('Count---constructor');
super(props)
// 初始化状态
this.state = { count: 0 }
}
// 加1按钮的回调
add = () => {
const { count } = this.state
this.setState({ count: count + 1 })
}
// 卸载组件按钮的回调
death = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 强制更新按钮的回调
force = () => {
this.forceUpdate()
}
// 组件将要挂载的钩子
componentWillMount() {
console.log('Count---componentWillMount');
}
// 组件挂载完毕的钩子
componentDidMount() {
console.log('Count---componentDidMount');
}
// 组件将要卸载的钩子
componentWillUnmount() {
console.log('Count---componentWillUnmount');
}
// 控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('Count---shouldComponentUpdate');
return true
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('Count---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('Count---componentDidUpdate');
}
render() {
console.log('Count---render');
const { count } = this.state
return (
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的参数,强制更新</button>
</div>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<Count />, document.getElementById('test'))
</script>
4. 父组件render
子组件将要接收新的props(componentWillReceiveProps)–> 组件是否应该更新(shouldComponentUpdate)–> 组件将要更新(componentwillUpdate)–> 组件渲染(render) --> 组件更新完毕(componentDidUpadte)
componentWillReceiveProps: 只有父组件状态发生改变重新调用render, 才会调用子组件的componentWillReceiveProps钩子,父组件第一次渲染时,子组件的这个钩子不会调用
第一次渲染:
重新render :
<script type="text/babel">
class A extends React.Component {
state = { carName: '奔驰' }
changeCar = () => {
this.setState({ carName: '宝马' })
}
render() {
return (
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName} /> {/*给B组件传数据*/}
</div>
)
}
}
class B extends React.Component {
// 组件将要接收新的props的钩子
componentWillReceiveProps(props) {
console.log('B---componentWillReceiveProps', props);
}
// 控制组件更新的“阀门”
shouldComponentUpdate() {
console.log('B---shouldComponentUpdate');
return true
}
// 组件将要更新的钩子
componentWillUpdate() {
console.log('B---componentWillUpdate');
}
// 组件更新完毕的钩子
componentDidUpdate() {
console.log('B---componentDidUpdate');
}
render() {
console.log('B---render');
return (
<div>
{/*B组件可以通过props拿到数据*/}
<div>我是B组件,接收到的车是:{this.props.carName}</div>
</div>
)
}
}
// 2. 渲染组件到页面
ReactDOM.render(<A />, document.getElementById('test'))
</script>
总结
- 初始化阶段:由ReactDOM.render()触发—初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount() ===> 常用
- 更新阶段:由组件内部this.setState()或父组件render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render() ===> 必须使用
- componentDidUpdate()
- 卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() ===> 常用 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息等