1.安装React 开发者工具调试
1.在Chrome应用商店中添加扩展程序:React Developer Tools
2.在github上下载压缩包:https://github.com/facebook/react-devtools/tree/v3
安装好之后运行react项目按F12打开浏览器控制台会多出两个tab栏:
● Components: 观察网页由多少个组件组成,每个组件有什么属性……
● Profiler: 用于记录网站性能,渲染时间,哪个组件加载的最慢……
本地调试使用 Liver Server
打开, alt b
是打开本地磁盘的内容调试工具无法使用
2.React中组件的基本理解和使用
定义组件的两种方式:函数式组件和类式组件
-复杂组件:有状态(state)
-简单组件:无状态(state)
2.1 函数式组件
示例图:
代码
<body><!-- 准备好一个容器 --><div id="test"></div><!-- 引入react核心库,全局多了React对象 --><script type="text/javascript" src="../js/react.development.js"></script><!-- 引入react-dom 用于支持react操作dom,全局多了ReactDOM对象 --><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">// 1.创建函数式组件function Demo() {return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>}// 2.渲染组件到页面ReactDOM.render(<Demo />, document.getElementById('test'))</script>
</body>
注意:
1.函数首字母必须大写,必须有返回值,必须要写组件标签,写好函数式组件后,由React调用。
2.函数中的this是 undefined,因为 babel 翻译后开启了严格模式(特点:禁止自定义函数的this指向window
)
3.执行了ReactDom.render(<Demo / >, document.getElementById('test'))
,发生了什么?
-1.React解析组件标签,找到了 Demo组件。
-2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
2.2 类式组件
示例图:
代码:
<script type="text/babel">// 1.创建类式组件: 必须继承React.Component,必须有render且有返回值class MyComponents extends React.Component {// render是放在MyComponents的原型对象上,供实例使用。// render中的this是【MyComponent的实例对象】(MyComponent组件实例对象)。render () {console.log('render中的this', this)return <h2>我是通过类式定义的组件(适用于【复杂组件】的定义)</h2>}}// 2.渲染组件到页面ReactDOM.render(<MyComponents/>, document.getElementById('test'))</script>
注意:
1.必须继承React.Component
,必须有render且必须有返回值
2.render是放在MyComponents的原型对象上,供实例使用;render中的this是【MyComponent的实例对
象】(MyComponent组件实例对象)。
3.执行了ReactDOM.render(<MyComponents/>, document.getElementById('test'))
后,发生了什么?
-1.React解析组件标签,找到了MyComponents组件
-2.React发现组件是使用类定义的,随后new出来MyComponents的实例,并通过该实例调用原型上的
render方法
-3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
3.组件实例的三大核心属性—state
3.1 state的基本使用
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
state的值是对象,表示一个组件中可以有多个数据
初始化状态:
this.state = { count:0 }
获取state
<div>{this.state.count}</div>
修改state
this.setState({count:this.state.count + 1
})
3.2 案例:点击标题切换内容
描述:点击标题文字,切换内容"凉爽" <=>“炎热”
示例图:
代码
class Weather extends React.Component {// 构造器调用了几次? —— 1次constructor(props){super(props);// 初始化状态this.state = {isHot: true,}// 解决changeWeather中的this指向问题: 把this改成Weather实例对象并生成一个新的函数this.toWeather = this.changeWeather.bind(this)}// render 调用了几次?——1+n次:初始化一次加状态更新的次数render() {// 读取状态const { isHot } = this.state;return <h1 onClick={this.toWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>}// changeWeather调用了几次? ——点几次调用几次changeWeather() {// changeWeather放在了Weather的原型对象上,供实例使用// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用的// 类中的方法默认开启局部的严格模式,所以changeWeather 中的this是undefined// console.log('点击了标题—changeWeather',this)// 获取原来的isHot值const isHot = this.state.isHot;// 状态不可直接更改,要借助一个内置的API-setState,不是替换是合并this.setState({ isHot: !isHot})}}ReactDOM.render(<Weather />, document.getElementById('test'))const title = document.getElementById('title');
总结:
1.构造器:this是该类的实例对象
1)用于初始化状态,只会调用一次
2)用于解决自定义函数中的this指向的问题
2.render:this是该类的实例对象
1)用于读取state状态,根据状态的值做展示
2)render中的 this 就是组件的实例对象
3.changeWeather:
1)主要做两件事:获取state中isHot的值 ,更改状态
2)状态不可直接更改,要借助一个内置的API—setState,且此操作是合并不是替换
4.为什么render中的this是类的实例对象?
ReactDOM.render(<Weather />, document.getElementById('test'));
react中 new Weather() 拿到了这个实例(w1),然后通过w1.render进行调用
5.为什么 changeWeather 中的this是undefined?
这是自定义的点击事件,是作为onClick的回调,所以不是通过实例调用的。由于类中的方法默认开启局部的
严格模式,所以changeWeather 中的this是undefined(解决方式是在构造器中使用bind)
3.3 State的简写方式
class Weather extends React.Component {constructor(props){super(props);// this.changeWeather = this.changeWeather.bind(this)}// 初始化状态state = {isHot1: true,}render() {const { isHot } = this.state;return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h1>}// 自定义方法:赋值语句 + 箭头函数,这样this就是组件实例对象changeWeather = () => {console.log(this)const isHot = this.state.isHot;this.setState({ isHot: !isHot})}
}
3.4 总结
1.组件自定义的方法中 this 为 undefined,如何解决?
-a. 强制绑定this:通过函数对象的bind()
-b.箭头函数
2.状态数据不能直接修改或更新
3.组件中render方法中的 this 为组件实例对象
4.组件实例的三大核心属性—props
4.1 props的基本使用
组件标签的所有属性都保存在 props 中
作用:
1.通过标签属性从组件外向组件内传递变化的数据
2.props 只读,组件内部不能修改 props 数据
使用:
1.读取某个属性值 this.props.name
2.扩展属性:将对象的所有属性通过 props 传递
<Person {...person}/>
3.对props中的属性值进行类型限制和必要性限制
● 方式1:(React v15.5 开始已弃用)
Person.propTypes = { name: React.PropTypes.string.isRequired }
● 方式2:(新,需引入 prop-types.js)
Person.propTypes = { name: PropTypes.string.isRequired }
4.设置默认属性值
Person.defaultProps = { age: 18, sex: '男' }
5.组件类的构造函数 - constructor
super()是否接收并传递props取决于是否希望在构造器中通过 this 访问 props,类中的构造器能省略就省略
● super(props):接收props并传递,可以通过实例访问 props
● super(): 无法通过实例访问props, 构造器中this.props 为undefined
// 创建组件class Person extends React.Component {render () {const { name, age, sex } = this.props;return (<ul><li>姓名:{ name }</li><li>性别:{ sex }</li><li>年龄:{ age }</li></ul>)}}
// 传递 props 方式1:
ReactDOM.render(<Person name="tom" age="18" sex="女" />, document.getElementById('test'))
ReactDOM.render(<Person name="jerry" age="20" sex="男" />, document.getElementById('test2'))// 传递 props 方式2:批量传递
const p = { name: 'tom', age: 18, sex: '女' }
ReactDOM.render(<Person {...p} />, document.getElementById('test'))
4.2 对props进行限制
4.2.1 对标签属性进行类型、必要性的限制以及默认值的设置
对标签属性进行类型、必要性的限制
Person.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,speak: PropTypes.func}
指定默认的标签属性值
Person.defaultProps = {sex: '不男不女',age: 18}
4.2.2 示例
// 创建组件class Person extends React.Component {render () {const { name, age, sex } = this.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,speak: PropTypes.func}// 指定默认的标签属性值Person.defaultProps = {sex: '不男不女',age: 18}ReactDOM.render(<Person name="jerry" age={20} sex="男" speak = {speak} />, document.getElementById('test2'))ReactDOM.render(<Person name="老刘" />, document.getElementById('test3'))function speak(){console.log('我说话了')}
4.3 props的简写方式
把对标签属性的限制及默认值的设置写在类中
不加 static 是加给类的实例,加上static是加在类本身
class Person extends React.Component {// 对标签属性进行类型、必要性的限制static propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,speak: PropTypes.func}// 指定默认的标签属性值static defaultProps = {sex: '男',age: 18}render () {const { name, age, sex } = this.props;return (<ul><li>姓名:{ name }</li><li>性别:{ sex }</li><li>年龄:{ age }</li></ul>)}}ReactDOM.render(<Person name="Jerry" />, document.getElementById('test'))
4.4 函数式组件使用props
通过函数可以接收参数的特性
// 对标签属性进行类型、必要性的限制Person.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,speak: PropTypes.func}// 指定默认的标签属性值Person.defaultProps = {sex: '男',age: 18}// 定义函数式组件function Person (props) {const { name, age, sex } = props;return (<ul><li>姓名:{ name }</li><li>性别:{ sex }</li><li>年龄:{ age }</li></ul>)}ReactDOM.render(<Person name="Jerry" />, document.getElementById('test'))
5.组件实例的三大核心属性—refs与事件处理
5.1使用ref的三种方式
- 字符串形式
- 定义:
<input ref="input1" />
- 使用:
const { value } = this.refs.input1;
- 定义:
- 回调函数形式
- 定义:
<input ref={(c) => this.input1 = c} />
- 使用:
const { value } = this.input1
- 定义:
- createRef
- 定义:
<input ref={this.myRef} />
- 使用:
const { value } = this.myRef.current
- 定义:
5.1.1 字符串形式的ref
在条件允许的情况下,尽可能避免使用字符串形式的ref
class Demo extends React.Component{render() {return (<div><input ref="input1" type="text" placeholder="点击按钮提示数据" /><button onClick={this.showData}>点击提示左侧数据</button> </div>)}showData = () => {const { value } = this.refs.input1;alert(value)}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
5.1.2 回调函数形式的ref
class Demo extends React.Component{render() {return (<div><input ref={(c) => this.input1 = c } type="text" placeholder="点击按钮提示数据" /><button onClick={this.showData}>点击提示左侧数据</button> </div>)}showData = () => {const { value } = this.input1;alert(value)}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
扩展: ref={(c) => this.input1 = c
内联形式的ref在创建时执行一次,更新页面时会执行两次。可以使用class 的绑定函数的方式来避免这个问题。但内联形式还是常用的。
class Demo extends React.Component{// 通过将ref的回调定义成 class 的绑定函数的方式可以避免每次渲染时会被执行两次的问题saveInput = (c) => {this.input1 = c;}render() {return (<div><input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" /><button onClick={this.showData}>点击提示左侧数据</button> </div>)}showData = () => {const { value } = this.input1;alert(value)}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
5.1.3 createRef的形式
1.React.createRef()
调用后可以返回一个容器,该容器可以存储ref所标识的节点;
2.this.myRef
是使用createRef创建的一个容器,把当前ref所在的节点(input)直接存储到这个容器里
3.需要几个ref就需要创建几个
class Demo extends React.Component{myRef = React.createRef();showData = () => {const { value } = this.myRef.current;alert(value)}render() {return (<div><input ref={this.myRef} type="text" placeholder="点击按钮提示数据" /><button onClick={this.showData}>点击提示左侧数据</button> </div>)}
}
ReactDOM.render(<Demo />, document.getElementById('test'));
5.2 事件处理
5.2.1事件绑定
1.通过 onXxx属性指定事件处理函数(注意大小写)
a. react事件绑定语法与DOM事件语法相似
- 语法:on + 事件名称 = { 事件处理程序} , 如: onClick = { () =>{} }
- React事件采用驼峰命名法,如:onMouseEnter、onFocus
a. React使用的是自定义(合成)事件,而不是使用的原生DOM事件——为了更好的兼容性
b. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——为了高效
2.通过event.target得到发生事件的DOM元素对象 ——不要过度使用ref
示例:
// 类式组件
class Header extends Component {// 按钮点击事件handleClick = (e) => {}render() {return (<div className="todo-header"><button onClick={this.handleClick}></button></div>)}
}
5.2.2 事件对象
可以通过事件处理程序的参数
获取到事件对象
function handleClick(e){e.preventDefault()console.log('事件对象:',e)
}
// 绑定事件
<a onClick={handleClick}></a>
6.组件生命周期
6.1 生命周期图(旧)
生命周期的三个阶段(旧)
- 初始化阶段执行的生命周期(4个) 由
ReactDOM.render()
触发
1.constructor()
2.componentWillMount() — 组件将要挂载时的钩子
3.render() — 组件挂载的钩子
4.componentDidMount() — 组件挂载完毕的钩子 - 更新阶段执行的生命周期(4个) 由组件内部
this.setState()
或父组件重新渲染 render 触发
1.shouldComponentUpdate — 控制组件是否更新的钩子,若返回 false,则下面的都不执行。
2.componentWillUpdate — 组件将要更新的钩子
3.render()
4.componentDidUpdate() — 组件更新完毕的钩子 - 卸载组件执行的生命周期(1个) 由
ReactDOM.unmountComponentAtNode()
触发
1.componentWillUnmount() — 组件将要卸载
作为子组件时,会先执行这个生命周期
- componentWillReceiveProps() — 组件将要接收新的props的钩子,第一次不执
shouldComponentUpdate返回false执行的生命周期(1个)
- shouldComponentUpdate()
forceUpdate() 强制更新执行的生命周期(3个)
- componentWillUpdate()
- render()
- componentDidUpdate()
常用的生命周期
- componentDidMount()
做一些初始化的事:开启定时器、发送网络消息、订阅消息 - componentWillUnmount()
做一些收尾的事:关闭定时器、取消订阅消息
6.2 生命周期(新)
react 版本17.0.1
生命周期的三个阶段(旧)
- 初始化阶段执行的生命周期(4个) 由
ReactDOM.render()
触发
1.constructor()
2.getDerivedStateFromProps()
3.render()
4.componentDidMount() - 更新阶段执行的生命周期(4个) 由组件内部
this.setState()
或父组件重新渲染 render 触发
1.getDerivedStateFromProps
2.shouldComponentUpdate
3.render()
4.getSnapshotBeforeUpdate()
5.componentDidUpdate() - 卸载组件执行的生命周期(1个) 由
ReactDOM.unmountComponentAtNode()
触发
1.componentWillUnmount()
static getDerivedStateFromProps (props, state) {console.log('getDerivedStateFromProps')return null;
}getSnapshotBeforeUpdate () {console.log('getSnapshotBeforeUpdate');return null;
}componentDidUpdate (preProps, preState, snapshotValue) {console.log('componentDidUpdate', preProps, preState, snapshotValue)
}
6.3 总结
重要的生命周期:
- render: 初始化渲染或重新渲染调用
- componentDidMount: 开启监听,发送ajax请求
- componentWillUnmount: 清理定时器……
即将废弃三个生命周期钩子,下一个大版本需要加前缀才能使用
- UNSAFE_componentWillMount()
- UNSAFE_componentWillReceiveProps()
- UNSAFE_componentWillUpdate()
新旧对比(17.0.1):
- 废弃了三个生命周期钩子:
- componentWillMount()
- componentWillReceiveProps()
- componentWillUpdate()
- 新增了两个生命周期钩子:
- getDerivedStateFromProps()
a. 要定义为静态方法,两个参数:props, state
b.要有返回值:null 或者 状态对象
c.场景:state的值在任何时候都取决于 props - getSnapshotBeforeUpdate()
a.任何返回值将作为参数传递给componentDidUpdate()
b.场景:组件在发送更改之前从DOM捕获一些信息(滚动位置)
7.关于表单处理
7.1 受控组件
其值受到React控制的表单元素
- html中表单元素时可输入的,也就是有自己的可变状态
- 而React中可变状态通常保存在state中,并且只能通过setState()方法来修改
- React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
步骤:
1.在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
2.给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)
<input type="text" value = { this.state.txt } onchange={this.handleContent}>