React 安装
安装命令(终端输入即可):npm i react react-dom
React基本使用
1.引入react和react-dom两个js文件
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
2.创建React元素
3.渲染React元素到页面中
<script>
//创建react元素 用 React.createElement(参数1,参数2,参数3);
//参数1:元素名称(标签) 参数2:元素属性 第三个及其以后的参数:元素的子节点(文本节点 const title = React.createElement('h1', {title:'我是标题',id:'h1'}, 'Hello React' );
//渲染react元素 用ReactDOM.render(参数1,参数2)
//参数1:要渲染的react元素 参数2:挂载点(要把元素渲染到哪里) ReactDOM.render(title, document.getElementById('root'));</script>
React脚手架的使用
1.使用react脚手架初始化项目
初始化项目,命令:npx create-react-app my-app
其中my-app是项目的名称 可以随便起名
启动项目,在项目根目录执行命令: npm start
2.在脚手架中使用react
1.导入react和react-dom两个包
import React from 'react'
import ReactDOM from 'react-dom'
2.调用React。createElement()方法创建react元素
3.调用ReactDOM.render()方法渲染react元素到页面
JSX的基本使用
1.JSX简介
JSX是JavaScript XML的简写,表示在JavaScript代码里面写XML格式的代码
2.使用步骤
//1.使用JSX创建React元素 推荐使用小括号包裹JSX 从而避免js中自动插入分号陷阱
const title = (<h1>Hello JSX</h1> )
//2 渲染React元素
ReactDOM.render(title,root)
3.React元素的属性名采用驼峰命名法
特殊属性名:class->className for ->htmlFor tabindex->tabIndex
4.嵌入JS表达式: {JavaScript表达式} 单括号
单大括号 可以使用任意JavaScript表达式 JS中的对象是一个例外,一般只会出现在style属性中
不能在大括号中出现语句
const name = 'Jack'
const dv = (
<div>你好,我叫{name}</div> //输出 你好,我叫Jack
)
5. JSX的列表渲染
-
如果要渲染一组数据,应该使用数组的map()方法
-
注意:渲染列表应该添加key属性,key属性的值要保证唯一
-
原则:map()遍历谁,就给谁添加key属性
-
尽量避免使用索引号作为key
const songs = [ {id:1, name:'痴心绝对'}, {id:2, name:'像我这样的人'}, {id:3, name:'南山南'}, ]
const list = (
-
{songs.map(item =>
- {item.name} }
6.JSX的样式处理
1)行内样式-style
const list = ( <h1 style={{color:'red',backgroundColor:'skyblue'}}>//第一个{}表示引入JS表达式,第二个{}表示对象 JSX的样式处理 </h1>)ReactDOM.render(list, document.getElementById('root'));
2)类名-className(推荐)
import "./CSS/index.css" //title类样式写入index.css里
const list = ( <h1 className="title"> JSX的样式处理 </h1>)ReactDOM.render(list, document.getElementById('root'));
2.React组件基础
1.React组件的两种创建方式
(1)使用函数创建组件
-
使用JS中的函数创建的组件叫做:函数组件
-
函数组件必须有返回值
-
组件名称必须以大写字母开头,React据此区分 组件 和 普通的React元素
-
使用函数名作为组件标签名
function Hello () { //函数名必须大写 return (
这是我的第一个函数组件//必须有返回值,如果不渲染任何东西,返回null ) }const Hello = () =>
这是我的第一个函数组件//箭头函数的写法 ReactDOM.render(,document.getElementById('root')) //单标签或者双标签都可以
(2)使用类创建组件
-
使用ES6的class创建的组件
-
约定1 类名称也必须以大写字母开头
-
约定2 类组件应该继承React.Compent父类 从而可以使用父类中提供的方法或属性
-
约定3 类组件必须提供render()方法
-
约定4 render()方法必须有返回值 表示该组件的结构
class Hello extends React.Component { render() { return (
这是我的第一个类组件//不想渲染任何东西 return null即可 ) } } ReactDOM.render( ,document.getElementById('root'))
2. 抽离为独立的JS文件
-
创建Hello.js
-
在Hello.js 中导入React
-
创建组件(函数或者 类)
-
在Hello.js 中导出该组件
-
在index.js中导入Hello组件
-
渲染组件
//Hello.js
import React from 'react' class Hello extends React.Component { render() { return (
这是我的第一个抽离到JS文件中的组件) } }export default Hello //导出Hello组件//index.js import Hello from './Hello' //渲染导入的Hello组件 ReactDOM.render( ,document.getElementById('root'))
3.React事件处理
1.事件绑定
语法:on+事件名称={事件处理程序},比如onClick={() => {}}
注意:React事件采用驼峰命名法,比如onMouseEnter、onFocus
class App extends React.Component {
handleClick() {
alert('触发了单击事件')
}
render() {
return (
<button onClick={this.handleClick}>点我,点我!!</button>
)
}
}
---------------------------------------------
//函数组件中
function App () {
function handleClick() {
console.log('单击事件触发了')
}
return (
<button onClick={handleClick}>点我,点我!!</button> //函数组件中不加this
)
}
2.事件对象
获取事件对象与之前的DOM一样,从事件处理程序中的参数就可以获取
React中的事件对象叫:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容问题
function handleClick(e) { //e就是事件对象--合成事件
e.preventDefault();
}
<a onClick={handleClick}>点我,不会跳转页面</a>
4.有状态组件和无状态组件
函数组件又叫无状态组件,没有自己的状态,只负责数据展示(静)
类组件又叫有状态组件,有自己的状态,负责更新UI,让页面动起来。
状态(state)即数据
state的基本使用:状态是私有的,只能在组件内部使用。通过this.state来获取状态
class App extends React.Component {
state = {
count: 0
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
</div>
)
}
}
setState()修改状态
语法:this.setState({要修改的数据})
注意:不要直接修改state的值,这是错误的
作用:1.修改state2.更新UI
//正确
this.setState({
count:this.state.count + 1
})
//错误
this.state.count += 1
`````````````````````````````````````
class App extends React.Component {
state = {
count: 0
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={() => {
this.setState({
count: this.state.count + 1
})
}}>+1</button>
</div>
)
}
}
5.事件绑定this指向
我们把事件函数尽可能抽离出来,在class里面写一个函数,而不是写在JSX里面,这样就比较条理有逻辑。但是要处理this的问题。因为写一个函数,事件处理函数里面的this指向的是调用者,而不是实例对象。
解决这个问题有三个办法
1)利用箭头函数没有this的特性
class App extends React.Component {
state = {
count: 0
}
onIncrement() {
console.log(this);
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={() => this.onIncrement()}>+1</button>
//箭头函数不绑定this,所以它的this就是上一级的render的this,也就是类,所以this.onIncrement()的this就是类App
</div>
)
}
}
2)bind方法
class App extends React.Component {
constructor(){
super(); //语法要求 构造函数里加上这一句
this.state : { //state的另一种写法,在constructor里面的写法
count: 0
}
this.onIncrement = this.onIncrement.bind(this); //bind方法改变this指向,但不立即调用 constructor里面的this也是对象/类
}
onIncrement() {
console.log(this);
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={() => this.onIncrement()}>+1</button>
</div>
)
}
}
3)class实例方法(推荐)
直接将事件处理程序写成箭头函数
注意:该语法是实验性语法,但是由于babel的存在可以直接使用
class App extends React.Component {
state = {
count: 0
}
onIncrement = () => { //直接写成箭头函数 这样他的this就是对象了
console.log(this);
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={() => this.onIncrement()}>+1</button>
</div>
)
}
}
6.表单处理
HTML中的表单元素是可以输入的,也就是有自己的可变状态。而React中可变状态通常保存在state中,并且只能通过setState()方法来修改。React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值
受到react控制的表单元素就是受控组件
步骤:
(1)在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
(2)给表单元素绑定change事件,将表单元素的值 设置为state的值(控制表单元素值的变化)
class App extends React.Component {
state = {
txt: ''
}
handleChange = e => {
this.setState({
txt: e.target.value //把value的值赋给state
})
}
change
render() {
return (
<div>
<input type="text" value={this.state.txt} onChange={this.handleChange}></input>
</div>
)
}
}
多表单元素优化:
如果有多个表单,那就要写多个事件处理程序,怪麻烦的。可以优化成只写一个事件处理程序
步骤:
(1)给表单元素添加name属性,名称与state相同
(2)根据表单元素类型获取对应值
(3)在change事件处理程序中通过[name]来修改对应的state
<input type="text" name="txt" value={this.state.txt} onChange={this.handleForm}/>
//根据表单元素类型获取值
handleForm = (e) => {
const target = e.target
const value = target.type === 'checkbox'? target.checked : target.value
const name = target.name
this.setState({
[name]:value
})
}
非受控组件方式处理表单
借助于ref 使用原生DOM方式来获取表单元素值(仅了解)
React组件进阶
1.组件通讯
多个组件之间共享某些数据,通过组件的props实现
2.组件的props:
接受传递给组件的数据
传递数据:给组件标签添加属性
<Hello name="jack" age={19} /> //传递非字符串类型的数据 要加个{}
接收数据:函数组件通过参数props接收数据,类组件通过this.props来接收数据
//函数组件
function Hello(props) {
console.log(props)
return (
<div>接收到数据:{props.name}</div>
)
}
//类组件
class Hello extends React.compoent {
render() {
return (
<div>接收到的数据:{this.props.age}</div>
)
}
}
props的特点:
-
可以给组件传递任意类型的数据
-
props是只读的对象,只能读取,不能修改
-
注意:使用类组件时,如果写了构造函数,应该将props传递给super() 否则 无法在构造函数中获取props
class Hello extends React.Component { constructor(props) { //构造函数比较特殊 需要在参数中写props super里也要出传递 super(props); } render() { return
接收到的数据:{this.props.age}} }
3.组件通讯的三种方式
- 父组件传递数据给子组件
2.子组件传递数据给父组件
利用回调函数,父组件提供回调函数,子组件调用,将要传递的数据作为回调函数的参数。
3.兄弟组件
4.Context
跨组件传递数据
(1)调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
const {Provider,Consumer} = React.createContext()
(2)使用Provider组件作为父节点
<Provider>
<div className="App">
<Child1 />
</div>
</Provider>
(3)设置value属性,表示要传递的数据
<Provider value="pink">
(4)调用Consumer组件接收数据
<Consumer>
{data => <span>data参数表示接收的数据 {data}</span>} //最近一层的Provider里面的value值会传递到参数data里面
</Consumer>
5.props深入
1.children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性.Children属性与普通的props一样,值可以是任意值。(文本,React元素,组件甚至是函数)
function Hello (props) {
return (
<div>
组件的子节点:{props.children}
</div>
)
}
<Hello>我是子节点</Hello>
2.props校验
对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据。如果传入的数据格式不对,可能导致组件内部报错。
pros校验允许在创建组件的时候,就指定props的类型和格式
3.props的默认值
6.组件的生命周期
组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时被卸载的过程
只有类组件才有生命周期
生命周期的每个阶段都伴随着一些方法调用,这些方法就是生命周期的钩子函数。这些钩子函数为开发人员在不同阶段操作组件提供了时机
生命周期的三个阶段
创建时:挂载阶段
render里面不能再调用setState(因为setState方法就会调用render,再在render里调用setState就会无限递归循环下去);DOM操作要在componentDidMount里面进行,render constructor里面是不行的
更新时(更新阶段)
本阶段有两个钩子函数render()和componentDidUpdate()
render方法的触发方式有三种:调用setState()、调用forceUpdate()、组件接收到新的props。以上三者任意一种变化,都会重新渲染组件
如果直接调用setState,那么就会触发更新,然后执行render和componentDidUpdate,然后就开始无限循环递归。放在if里面的做法:
//比较更新前后的props是否相同,来决定是否重新渲染组件
componentDidUpdate(prevProps) {
//prevProps是上一次的props this.props是这一次的
if (prevProps.count !== this.props.count){ //count是props里的一个属性,这里的例子中判断count是否变化
this.setState({});
//发送ajax请求代码也在if里面
}
}
卸载时(卸载阶段)
7.render-props和高阶组件
如果两个组件中的部分功能相似或者相同,就需要复用相似的功能、复用什么?1state 2 操作state的方法
有两种方式:render-prop模式 和 高阶组件
render-props模式
import img from './image/cat.png' //注意react中使用图片的方法 先import
class Mouse extends React.Component { //要复用的state和操作state的方法封装在Mouse里面
//这是一个获取鼠标位置的组件
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)//props里传一个函数render,然后这里调用这个函数render,并把state传参进去
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
<Mouse render={(mouse) => { //传参 render函数,具体怎么实现在这里写
return <p>鼠标位置:{mouse.x} {mouse.y}</p>
}} />
<Mouse render={mouse => { //传参 render函数,具体怎么实现在这里写
return <img src={img} alt="猫" style={{
position: 'absolute',
top: mouse.y - 64,
left: mouse.x - 64
}}></img>
}} />
</div>
)
}
}
高阶组件
//创建高阶组件
function withMouse (WrappedComponent) {
//该组件提供复用的状态逻辑
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);
}
componentWillMount(){
window.removeEventListener('mousemove',this.handleMouseMove)
}
render() {
return <WrappedComponent {...this.state} />
}
}
return Mouse
}
const Position = props => (
<p>
鼠标当前位置:(x:{props.x}, y:{props.y})
</p>
)
const MousePosition = withMouse(Position)
class App extends React.Component {
render() {
return (
<div>
<MousePosition></MousePosition>
</div>
)
}
}
设置displayName:
原因:高阶组件在调试的时候还是显示同样的名字:比如上段代码中的组件,在调试的时候显示的还是. 原因:默认情况下,React使用组件名称作为displayName。
解决办法:为高阶组件设置displayName便于调试时区分
function withMouse (WrappedComponent) {
//省略Mouse里面的代码
class Mouse extends React.Component{
}
//displayName设置
Mouse.dispalyName = `WithMouse${getDisplayName(WrappedComponent)}`
return Mouse
}
function getDisplayName(WrappedComponent) {
return WrappedComponent.dispalyName || WrappedComponent.name || 'Component'
}//尽量按照这种固定模式来写
const Position = props => (... //略
const MousePosition = withMouse(Position) //这样在调试的时候组件名显示为 WithMousePosition
传递props
React原理
1.setState()说明
setState更新数据是异步的,所以同一个函数里面后面的setState()不要依赖于前面的setState().同一个函数里可以调用多次setState(),但是只会触发一次重新渲染
this.state = {count:1}
this.setState({
count:this.state.count + 1
})
console.log(this.state.count) //1
setState推荐语法:
为了解决异步的问题
this.setState((state,props) => { //参数state表示最新的state 参数props表示最新的props
return {
count: state.count + 1
}
})
//这种方法也是异步的 不过参数state可以拿到最新的
setState()的第二个参数:
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(update[,callback]) 第二个参数为回调函数
this.setState(
(state,props) => {},
() => {console.log('这个回调函数会在状态更新后立即执行')}
)
2.组件更新机制
3.组件性能优化
这个钩子函数里面的参数nextProps nextState是最新的值。要得到更新前的状态用this.state
纯组件方式:
注意:纯组件方式 内部的对比是shallow compare(浅层对比),state中对于数值型数据来说,直接赋值,没有坑。但是对于引用类型来说,只比较对象的引用地址是否相同。如果只是修改了对象中的某个属性,但是对比发现内存地址没有变。所以,state或者props中属性值为引用类型时,应该创建新数据,不要直接修改原始数据。
虚拟DOM和Diff算法
组件的render()调用后,根据状态和JSX结构生成虚拟DOM对象。render方法并不意味着浏览器的重新渲染,仅仅说明要进行diff算法。
React路由
前端路由功能:让用户从一个视图(页面)导航到另一个视图(页面)
前端路由是一套映射规则,在React中 是URL路径与组件的对应关系
1.路由的基本使用
1.安装: npm i react-router-dom
2.导入路由的三个核心组件Router、Route、Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
3.使用Router组件包括整个应用(之后在被包括的部分才可以用Link和Route来匹配URL和组件)
4.使用 Link组件作为导航菜单(路由入口)
<Link to="/first">页面一</Link>
5.使用Route组件配置路由规则和要展示的组件(路由出口)
完整案例:
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
const First = () => <p>页面一的内容</p>
const App = () => (
<Router>
<div>
<h1>React路由基础</h1>
<Link to="/first">页面一</Link>
<Route path="/first" component={First}></Route>
</div>
</Router>
)
2.常用组件说明
- Router组件:包裹整个应用,一个React应用只需要应用一次
- 两种常用的Router:HashRouter和BrowserRouter
- HashRouter:使用URL的哈希值实现(localhost:3000/#/first),只需要把BrowserRouter改成HashRouter,剩下的都不用改。不过一般不用
- Link组件:用于指定导航链接(a标签)
- Route组件:指定路由展示组件相关信息
3.路由的执行过程
4.编程式导航
通过JS代码来实现页面跳转
-
history是React路由提供的,用于获取浏览器历史记录的相关信息
-
push(path)跳转到某个页面,参数path表示要跳转的路径 -
go(n):前进或者后退某个页面,参数n表示前进或者后退的页面数量(比如:-1表示后退到上一页)this.props.history.go()class Login extends Component { handleLogin = () => { //实现登录后跳转 // ... this.props.history.push('/home') } render() {...省略其他代码} }
5.默认路由
表示进入页面时就会匹配的路由
默认路由path为:/
<Route path="/" component={Home} />
6.匹配模式
1.模糊匹配
默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要pathname(to后面的)以path(Route组件的path)开头 就会匹配成功
<Link to="/login">登录页面</Link>
<Route path="/" component={Home} /> //Link也会匹配成功这个Home
2.精确匹配
给Route组件添加exact属性,让其变成精确匹配模式
精确匹配:只有当path和pathname完全匹配时才会展示该路由
<Route exact path="/" component=.../>
//这种情况只能匹配pathname="/"这一种情况
推荐给默认路由添加exact属性