1、React是构建用户界面的Javascript库
2、初始化项目命令:npx craete-react-app my-app
3、启动命令:yarn start (或npm start)
4、React.createElement()方法用于创建react元素
创建react元素 (参数:元素名称,元素属性,第三个参数及其后参数都是元素子节点)
5、ReactDOM.render()方法负责渲染react元素到页面中
渲染react元素(参数:要渲染的react元素,页面挂载点)
// 使用JSX创建react元素
const name = 'lilei'
const title = (
你好,{name}
)
// 渲染
ReactDOM.render(title, document.getElementById('root'))
场景:loading效果
根据条件渲染特定的JSX结构(可以使用if-else,三元运算符,逻辑与运算符来实现)
// 条件渲染
let isLoading = true
// 1、if-else
const loadData = () => {
if (isLoading) {
return loading...
}
return 加载完成的数据
}
// 2、三元表达式
// const loadData = () => {
// return isLoading ? loading... : 加载完成的数据
// }
// 3、逻辑与运算符
// const loadData = () => {
// return isLoading && 加载完成的数据
// }
const title = (
条件渲染:{loadData()}
)
ReactDOM.render(title, document.getElementById('root'))
渲染一组数据,应该使用数组的map()方法
渲染列表时应该添加key属性,key属性的值要保证唯一
原则:map()遍历谁,就给谁添加key属性
注意:尽量避免使用索引号作为key
const arr = [{ id: 1, name: 'lilei' }, { id: 2, name: 'hanmeimei' }, { id: 3, name: 'dada' }, { id: 4, name: 'xiaoxiao' }]
const list = (
{arr.map(item => - {item.name}
)}
)
ReactDOM.render(list, document.getElementById('root'))
方法:行内样式和添加className类名
// JSX样式处理
const text = (
JSX样式
)
ReactDOM.render(text, document.getElementById('root'))
(函数名必须以大写字母开头,必须有返回值)
const Hello = () => 这是一个组件
ReactDOM.render( , document.getElementById('root'))
类名称必须大写字母开头
类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
类组件必须提供render()方法
render()方法必须有返回值,表示该组件的结构
// 创建类组件
class Hello extends React.Component {
render() {
return (
这是一个类组件
)
}
}
// 渲染组件
ReactDOM.render( , document.getElementById('root'))
将组件抽离为独立的js文件再引入使用
import React from 'react'
// 创建组件
class Hello extends React.Component {
render() {
return (
这是一个抽离到js文件中的类组件
)
}
}
// 导出组件
export default Hello
// 导入组件
import Hello from './conponents/Hello'
// 渲染组件
ReactDOM.render( , document.getElementById('root'))
React事件绑定语法与DOM事件语法相似
语法:on+事件名称={事件处理程序},比如{()=>{}}
注意:React事件采用驼峰命名法,如:onMouseEnter,onFocus
类组件形式写法:
// 事件绑定
class App extends React.Component {
// 事件处理程序
handleclick() {
console.log('点击事件执行了');
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
函数组件形式写法:(函数组件中没有this)
function App() {
// 事件处理程序
function handleclick() {
console.log('点击事件执行了');
}
return (
)
}
ReactDOM.render( , document.getElementById('root'))
React中的事件对象叫做合成事件(对象)
事件对象e
阻止浏览器的默认行为 e.preventDefault()
// 事件对象
function App() {
// 事件处理程序
function handleclick(e) {
e.preventDefault()
console.log('点击事件执行了');
}
return (
点击
)
}
ReactDOM.render( , document.getElementById('root'))
函数组件又叫做无状态组件,类组件又叫做有状态组件
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新UI,让页面“动”起来
状态即数据
状态是私有的,只能在组件内部使用
通过this.state来获取状态
class App extends React.Component {
// constructor() {
// super()
// // 初始化state
// this.state = {
// count: 0
// }
// }
// 简化语法初始化state
state = {
count: 0
}
render() {
return (
计数器:{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
状态是可变的
语法:this.setState({要修改的数据})
注意:不要直接该state中的值,是错误的
setState()作用:1.修改state 2.更新UI
思想:数据驱动视图
class App extends React.Component {
state = {
count: 0
}
render() {
return (
计数器:{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
事件处理程序中this的值为undefined
三种方法:箭头函数、Function.prototype.bind()、class的实例方法
class App extends React.Component {
state = {
count: 0
}
changeCount() {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
计数器:{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
class App extends React.Component {
constructor() {
super()
this.changeCount = this.changeCount.bind(this)
}
state = {
count: 0
}
changeCount() {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
计数器:{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
脚手架中babel存在可以直接使用
class App extends React.Component {
state = {
count: 0
}
changeCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
计数器:{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
class App extends React.Component {
state = {
txt: ''
}
changeTxt = (e) => {
this.setState({
txt: e.target.value
})
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
同时处理
class App extends React.Component {
state = {
txt: '',
city: 'bj',
isChecked: true
}
changeHandle = (e) => {
const target = e.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
class App extends React.Component {
constructor() {
super()
// 创建ref
this.txtRef = React.createRef()
}
getTxt = () => {
console.log('文本框的值为', this.txtRef.current.value);
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
效果图:
// 发表评论案例
class Comment extends React.Component {
state = {
name: '',
content: '',
commentList: []
}
// 处理表单元素值
handleForm = (e) => {
const { value, name } = e.target
this.setState({
[name]: value
})
}
// 发表评论
handlePublish = () => {
const { commentList, name, content } = this.state
// 非空校验
if (name.trim() === '' || content.trim() === '') {
alert('请输入评论内容和评论人')
return
}
const newCommentList = [{
id: Math.random(),
name: name,
content: content
}, ...commentList]
this.setState({
name: '',
content: '',
commentList: newCommentList
})
}
renderList = () => {
const { commentList } = this.state
if (commentList.length === 0) {
return (
暂无评论
)
}
return (
{commentList.map(item =>
评论人:{item.name}
评论内容:{item.content}
)}
)
}
render() {
const { name, content } = this.state
return (
{this.renderList()}
)
}
}
ReactDOM.render( , document.getElementById('root'))
组件是封闭的,要接受外部数据应该通过props来实现
props的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数props接收参数,类组件通过this.props接收数据
特点:
1、可以给组件传递任意类型的数据(函数,JSX结构)
2、props是只读的对象,只能读取属性的值,无法修改对象
3、注意:实用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props
const Components = props => {
console.log(props);
return (
{props.title}
{props.count}
)
}
// 传递数据
ReactDOM.render( , document.getElementById('root'))
class Components extends React.Component {
render() {
this.props.fn()
return (
{this.props.title}
{this.props.count}
{this.props.tag}
)
}
}
// 传递数据
ReactDOM.render( { console.log('这是一个函数') }} tag={这是一个JSX结构的标签} />, document.getElementById('root'))
1、父组件提供要传递的state的数据
2、给子组件标签添加属性,值为state中的数据
3、子组件中通过props接收父组件中传递的传递的数据
// 父传子
class ParentComponents extends React.Component {
state = {
title: '这是父组件的标题'
}
render() {
return (
这是子组件:
)
}
}
class SonComponents extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
{this.props.title}
)
}
}
ReactDOM.render( , document.getElementById('root'))
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
1、父组件提供一个回调函数(用于接收数据)
2、将该函数作为属性值,传递给子组件
3、子组件通过props调用回调函数
4、将子组件的数据作为参数传递给回调函数
注意:回调函数中this指向问题
// 子传父
class ParentComponents extends React.Component {
state = {
msg: ''
}
getChildMsg = (data) => {
this.setState({
msg: data
})
}
render() {
return (
父组件:{this.state.msg}
)
}
}
class ChildComponents extends React.Component {
state = { msg: '我是子组件的一条信息' }
handleClick = () => {
this.props.getMsg(this.state.msg)
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
思想:状态提升
公共父组件职责:1、提供共享状态2、提供操作共享状态的方法
要通讯的子组件只需要通过props接收状态或操作状态的方法
class ParentComponents extends React.Component {
// 提供共享状态
state = {
count: 0
}
// 提供修改状态的方法
changeCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
)
}
}
const Child1 = props => {
return (
计数器:{props.count}
)
}
const Child2 = props => {
return (
)
}
ReactDOM.render( , document.getElementById('root'))
children属性:表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
children属性与普通的props一样,值可以是任意值(文本,React元素,组件,甚至是函数)
const App = props => {
return (
组件标签的子节点:{props.children}
)
}
ReactDOM.render(我是子节点 , document.getElementById('root'))
const App = props => {
return (
组件标签的子节点:{props.children}
)
}
ReactDOM.render(
我是子节点,是一个P标签
, document.getElementById('root'))
const App = props => {
return (
组件标签的子节点:{props.children}
)
}
const Test = () => ()
ReactDOM.render(
, document.getElementById('root'))
const App = props => {
return (
组件标签的子节点:{props.children()}
)
}
ReactDOM.render(
{
() => { console.log('这是一个函数子节点') }
}
, document.getElementById('root'))
使用步骤:
1、安装包props-types(npm i prop-types / yarn add prop-types)
2、导入prop-types包
3、使用组件名。propTypes={}来给组件的props添加校验规则
4、校验规则通过PropTyprs对象来指定
props校验:允许在创建组件的时候。就指定props的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
import propTypes from 'prop-types'
const App = props => {
const arr = props.colors
const lis = arr.map((item, index) => {
return ({item.name} )
})
return {lis}
}
// 添加props校验
App.propTypes = {
colors: propTypes.array
}
ReactDOM.render( , document.getElementById('root'))
约束规则:
1、常见类型:array,bool,func,number,object,string
2、React元素类型:element
3、必填项:isRequired
4、特定结构对象:shape({})
// 添加props校验
// 属性a的类型:数值
// 属性fn的类型:函数(func)并且为必填项
// 属性tag的类型:React元素(element)
// 属性filter的类型:对象({area:'上海',price:1900})
App.proptypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
tag: PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
ReactDOM.render( , document.getElementById('root'))
场景:分页组件 ——》每页显示条数
作用:给props设置默认值,在未传入props时生效
// props的默认值
const App = props => {
console.log(props);
return (
props的默认值:{props.page}
)
}
// 添加props默认值
App.defaultProps = {
page: 1
}
ReactDOM.render( , document.getElementById('root'))
使用步骤:
1、创建Mouse组件,在组件中提供服用的状态逻辑代码(1.状态 2.操作状态的方法)
2、将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3、使用props.render{}的返回值作为要渲染的内容
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY,
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
render() {
return this.props.render(this.state)
}
}
class App extends React.Component {
render() {
return (
render-props:
{
return (
鼠标位置:{mouse.x} {mouse.y}
)
}
}>
)
}
}
ReactDOM.render( , document.getElementById('root'))
class Mouse extends React.Component {
state = {
x: 0,
y: 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY,
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
render() {
return this.props.children(this.state)
}
}
class App extends React.Component {
render() {
return (
render-props:
{
mouse => {
return (
鼠标位置:{mouse.x} {mouse.y}
)
}
}
)
}
}
ReactDOM.render( , document.getElementById('root'))
使用步骤:
1、创建一个函数,名称约定以with开头
2、指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3、在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4、再该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5、调用高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
// 创建高阶组件
function withMouse(WrappedComponent) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0
}
hamdleMousemove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener('mousemove', this.hamdleMousemove)
}
componentWillUnmount() {
window.removeEventListener('mouseover', this.hamdleMousemove)
}
render() {
return
}
}
return Mouse
}
// 用来测试高阶组件
const Position = props => (
鼠标当前位置:(x:{props.x},y:{props.y})
)
// 调用高阶组件来增强猫捉老鼠组件
const Cat = mouse => (
鼠标位置:{mouse.x} {mouse.y}
)
const MousePosition = withMouse(Position)
const MouseCat = withMouse(Cat)
class App extends React.Component {
render() {
return (
高阶组件
)
}
}
ReactDOM.render( , document.getElementById('root'))
使用高阶组件存在的问题:得到的两个组件名称相同
原因:默认情况下,React使用组件名称作为displayName
解决方式:为高阶组件设置displayName便于调试时区分不同的组件
displayName的作用:用于设置调试信息(React Developer Tools信息)
function withMouse(WrappedComponent) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0
}
hamdleMousemove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener('mousemove', this.hamdleMousemove)
}
componentWillUnmount() {
window.removeEventListener('mouseover', this.hamdleMousemove)
}
render() {
return
}
}
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'component'
}
return Mouse
}
问题:props丢失
原因:高阶组件没有往下传递props
解决方式:渲染WrapperedComponent时,将state和this.props一起传递给组件
function withMouse(WrappedComponent) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0
}
hamdleMousemove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener('mousemove', this.hamdleMousemove)
}
componentWillUnmount() {
window.removeEventListener('mouseover', this.hamdleMousemove)
}
render() {
return
}
}
setState()是异步更新数据的
注意:使用该语法时,后面的setState()不要依赖于前面的setState()
可以多次调用setState(),只会触发一次重新渲染
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
console.log(this.state.count);
}
render() {
return (
{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
推荐:使用setState((state,props)=>{})语法,这种方法也是异步更新state
参数state:表示最新的state
参数 props:表示最新的props(在状态更新后立即执行某个操作,回相当于回调函数)
this.setState((state, props) => {
return {
count: state.count + 1
}
})
class App extends React.Component {
state = {
count: 1
}
handleClick = () => {
this.setState((state, props) => {
return {
count: state.count + 1
}
},
// 状态更新后并且重新渲染后立即执行
() => {
console.log('更新状态完成:' + this.state.count);
})
this.setState((state, props) => {
return {
count: state.count + 1
}
})
}
render() {
return (
{this.state.count}
)
}
}
ReactDOM.render( , document.getElementById('root'))
减轻state:只存储根组件渲染相关的数据(比如:count / 列表数据 / loading等)
注意:不用做渲染的数据不要放在state中,不如定时器id等
对于这种需要在多个方法中用到的数据,应该放在this中
class Hello extends React.Component{
componentDidMount(){
this.timerId=setInterval(()=>{},2000)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
render(){...}
}
组件更新机制:父组件更新也会引起子组件也被更新
问题:子组件没有任何变化时也会重新渲染
如何避免不必要的重新渲染呢?
解决方式:使用钩子函数shouldComponentUpdate(nextProps,nextState)
作用:通过返回值确定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate→render)
class Hello extends React.Component{
shouldComponentUpdate(){
// 根据条件,决定是否重新渲染组件
return false
}
render(){...}
}
nextState:
class App extends React.Component {
state = {
number: 0
}
// 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
shouldComponentUpdate(nextProps, nextState) {
console.log('最新状态:', nextState, '当前状态:', this.state);
return nextState.number !== this.state.number
// if (nextState.number === this.state.number) {
// return false
// }
// return true
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
render() {
console.log('执行了render');
return (
随机数:{this.state.number}
)
}
}
ReactDOM.render( , document.getElementById('root'))
nextProps:
class NumberBox extends React.Component {
// 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
shouldComponentUpdate(nextProps, nextState) {
console.log('最新状态:', nextProps, '当前状态:', this.props);
return nextProps.number !== this.props.number
// if (nextState.number === this.state.number) {
// return false
// }
// return true
}
render() {
console.log('执行了render');
return (
随机数:{this.props.number}
)
}
}
class App extends React.Component {
state = {
number: 0
}
handleClick = () => {
this.setState(() => {
return {
number: Math.floor(Math.random() * 3)
}
})
}
render() {
return (
)
}
}
ReactDOM.render( , document.getElementById('root'))
① 安装
npm install react-router-dom
② 导入路由的三个核心组件
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
③ 使用Router组件包裹整个应用(重要)
const App = () => {
return (
React路由基本使用
)
}
ReactDOM.render( , document.getElementById('root'))
④使用Link组件作为导航菜单(路由入口)
页面一
⑤使用Route组件配置路由规则和要展示的组件(路由出口)
新版本React
// 使用Router组件包裹整个应用
const App = () => (
React路由基本使用
{/* 指定路由入口 */}
页面一
{/* 指定路由出口 */}
} />
)
ReactDOM.render( , document.getElementById('root'))
捞版本React