前言:
学习一个新东西的开始就是先运行跑起来,跟着官网敲了一遍棋盘游戏 使用JSX而不是JS
接触到:
① props传递数据 ② 为避免this困扰 用箭头函数 ③ 构造函数以super(props)开头 ④ this.state去记忆 ⑤ React Devtools
为什么不用js而是用jsx
1
2
3
4
5
6// 1.jsx
const VDOM = (
<h1 id="title">
<span>Hello</span>
</h1>
)1
2// 2.js
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello'))jsx经过babel翻译之后,变成js的格式,可以被识别
即jsx创建虚拟DOM是js这种创建方式的语法糖
虚拟DOM VS 真实DOM
关于虚拟DOM:
1.本质是Object类型的对象
2.虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性
3.虚拟DOM最终会被React转化为真实DOM,呈现在页面上jsx语法规则
1.定义虚拟DOM时,不要写引号
2.标签中混入JS表达式时要用{}
3.样式的类名指定不要用class,要用className
4.内联样式,要用双括号key:value对的形式去写
5.只有一个根标签,且必须闭合
6.标签首字母
(1)若小写字母开头,则将标签转为html中的同名元素,若html中无该标签对应的同名元素,则报错。虽然报错,但是不干扰其他元素的显示【报错信息:good不能被识别,您是否是在用组件,组件应该大写字母开头】
(2)若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。报错且页面空白【报错信息:找不到这个组件】
一个小案例:以li的方式动态显示数据
1
2
3
4//数据是array 在VDOM中
data.map((item,index)=>{
return <li key={index}>{item}</li>
})要记得写index,类似vue 就是diff算法 需要一个key
1. 基础语法
1.1 组件
1.1.1创建组件的两种方式
- 函数式组件 【简单组件】
hooks
1 | //1.创建函数式组件 因为是组件所以首字母应该大写 记得return |
执行渲染过程:
1.React解析组件标签,找到Mycomponent组件
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转真实DOM,随后呈现在页面上
- 类式组件 【复杂组件】复杂就是有state
1 | class MyComponent extends React.Component { |
执行了ReactDOM.render(<MyComponent/>)
之后,发生类什么?
1.React解析组件标签,找到MyComponent组件
2.发现组件是类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
3.将返回的虚拟DOM转为真实DOM,随后呈现在页面上
1.1.2组件的三大属性
- state
- props
- refs与事件处理
1.2 state
绑定事件监听
<h onClick={demo}></h>
function demo() {}
一个小案例:点击切换页面数据(今天天气hot?cool)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.state = { isHot: false }
//改变this
this.changeWeather = this.changeWeather.bind(this)
}
render() {
const {isHot} = this.state
return <h2 id="title" onClick={this.changeWeather}>今天天气很{isHot ? 'hot' : 'cool'}</h2>
}
changeWeather() {
//响应事件写在外面的话 这个地方写this this是undefined 因为babel开启了严格模式 就算不开启严格模式 this也是windows 调不到this.state.isHot
//现在我将它写到了类的里面 作为方法 结果this为什么还是undefined
//这是因为changeWeather是作为onClick的回调 所以不是通过实例调用 是直接调用
//出现undefined 是因为类中的方法默认开启了局部的严格模式
//解决方式 去通过bind改变this
//获取原来的isHot值
const isHot = this.state.isHot
//严重注意:state不能直接更改 通过一个内置的API去更改
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))即:
1.通过事件去改变状态 1)要在构造函数中通过响应事件去bind(this) 并把结果赋给onClick的回调 2)箭头函数 2.开启严格模式的话 this会由window变为undefined 。1)类中的方法会开启 2)babel会开启 3.不能直接更改state,需要通过this.setState去更改(合并操作,不会影响this.state里的其他属性)
进行简化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class MyComponent extends React.Component {
constructor(props) {
super(props)
}
state = { isHot: false }
render() {
const {isHot} = this.state
return <h2 id="title" onClick={this.changeWeather}>今天天气很{isHot ? 'hot' : 'cool'}</h2>
}
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
1.3 props
1 | //对标签属性进行类型,必要性限制 |
进行限制的时候,number string 这些首字母应该小写 否则会与内部的Number,String 产生冲突
function要避免冲突 写为func
function因为this的原因 不能玩state和refs 但是可以玩props。(因为props本身可以作为function的参数)
1 | function Person(props) { |
1.4 refs
勿过度使用,其实用别的方式也可以访问DOM或某个组件的实例
Sring的Refs是过时的 不建议使用 存在效率问题 官方推荐:用回调函数或createRef API
回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Demo extends React.Component{
//展示数据
showData = () => {
const {input1} = this
console.log(input1.value);
}
render(){
return(
<div>
<input ref={currentNode => this.input1 = currentNode} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>click</button>
</div>
)
}
}ref写回调的含义:拿到当前ref所在的节点 react帮着调这个函数传进去 挂到this实例自身的属性input1上
上面写的是内联函数,更新过程会被执行2次 第一次null 第二次传入参数DOM
可以通过类绑定函数的方式避免这个问题,类绑定函数避免这个问题。大多数情况下都是无关紧要的。
createRef API
React.createRef()调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”
1
2
3
4myRef = React.createRef() //创建容器
console.log(this.myRef.current.value); //拿节点的值
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
1.5 事件处理
(1)通过onXxx属性指定事件处理函数(注意大小写)
a. React使用的是自定义(合成)事件,而不是使用的原生DOM事件-----为了更好的兼容性
b. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)-----为了更高效
(2)通过event.target得到发生事件的DOM元素对象
ps: 如果数据和事件在同一个节点上 那就可以用这种方式拿数据 可以不用ref
1.6 处理表单
非受控组件 VS 受控组件
非受控组件:
1 | <form onSubmit={this.handleSubmit}> |
受控组件:
1 | //保存用户名到状态 |
(1)非受控组件的每一个数据项都要用ref
(2)受控组件的数据可以维护到状态中,也可以从状态中取出来(感觉类似vue的双向绑定)
官网建议用 受控组件
一个收集表单数据的小案例
- 用高阶函数+函数柯里化的方式收集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//创建组件
class Login extends React.Component {
state = {
username:'',
password:''
}
//保存到状态 高阶函数+函数柯里化
saveFormData = (dataType)=>{
return (event)=>{
console.log({[dataType]:event.target.value});
}
}
handleSubmit = (event)=>{
event.preventDefault() //阻止表单提交
const {username,password} = this.state
console.log(`用户名:${username.value},密码:${password.value}`);
}
render(){
//this.saveFormData() 将函数返回值作为onChange回调
//this.saveFormData 将函数作为onChange回调
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
<button>登录</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById('test'))为了抽象出方法 简化代码
将保存信息的方法进行抽象 —–> 为了区别保存的是谁 —–> 所以将ID作为参数 —–> 因为写了括号 会将函数返回值作为onChange的回调 —–> 所以函数体里面return一个箭头函数 —–> 最后执行的是return里面的内容
也可以不用柯里化
1
2
3
4
5
6
7
8
9
10
11
12
13
14//保存到状态 不用函数柯里化
saveFormData = (dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={ event => this.saveFormData('username',event) } type="text" name="username" />
密码:<input onChange={ event => this.saveFormData('password',event) } type="password" name="password" />
<button>登录</button>
</form>
)
}因为onChange需要一个函数—–> 所以()=>{} 通过react去调用 —–> 拿到event.target.value —–> 再把value作为参数传入this.saveFormData去调用
1.7 生命周期
- 一个小案例:文字按每次0.1渐隐,到opacity为0的时候,再变为1。并且保证按钮可以正常点击,去卸载组件
处理渐隐渐显 和 卸载组件的代码:
1 | death = ()=>{ |
定时器不能放在render
因为render会调用1+n次 ,this.setState({opacity})会无限的调render —-> 就炸了
渐隐和渐显的效果可以出来:
但是再去点击按钮的时候:
报错信息:不能在卸载组件上执行状态更新
原因是:按钮是触发了 death 的回调去卸载组件 而componentDidMount里的定时器还在更新state
解决:在卸载之前要先去clear定时器
现在的问题就变成了怎么去清除:通过绑定给this一个属性 将该属性作为定时器的ID去清除
方式一:放在death中 卸载前
1
2
3
4
5
6
7
8
9
10death = ()=>{
//清除定时器
clearInterval(this.timer)
//卸载组件
}
componentDidMount(){
this.timer = setInterval(() => {
//
},200)
}
方式二: 放在componentWillUnmount中
1
2
3
4
5//组件将要卸载
componentWillUnmount(){
//清除定时器
clearInterval(this.timer)
}
总结一下生命周期回调函数:
2. 进阶语法
2.1 虚拟DOM
数据—–>虚拟DOM—–>真实DOMß
diff比较规则:
a.旧DOM中找到了与新DOM相同的key:
i)若虚拟DOM的内容没变,直接使用之前的真实DOM
ii)若虚拟DOM的内容变了,替换
b.没有找到相同的key:根据数据创建新的真实DOM,随后渲染到页面
用index作为key可能会出现问题:
- 效率问题:2000条数据前面加一条,用index将位置作为key ,会造成2001条都要进行更新(显然这是一个大问题,对于原来的2000条数据更新是完全没有必要的)
- 结构中若包含输入类DOM:产生错误的DOM更新,使得输入框的DOM信息错误,界面有问题
因此index不建议去唯一标识数据
2.2 脚手架
使用在html里面写script引入react核心库,react-dom,babel。然后写type='text/babel'
让浏览器去将写的jsx翻译为js 。这种方式可能会出现一个问题:白屏(代码量大 翻译不过来)
官网的环境配置流程:
1 | npx create-react-app 项目名 |
npm run build 会生成优化版本
jsx文件,安装ES7…插件之后
- rcc 类定义的组件
- rfc 函数定义的组件
2.3 react ajax
React本身只关注界面,并不包含发送ajax请求的代码。即没有React.get(url)这种写法
可以通过axios或者fetch去处理请求
1 | fetch('url').then( |
2.4 组件通信
父向子:通过props
1
2父:return (<Child fatherToChild = {data} />)
子:{this.props.fatherToChild}使用context可以避免中间元素传递
PubSub
和vue里用的消息订阅发布是同一个js库,微信小程序也可以用
1 | //订阅消息 常写在componentDidMount |
2.5 React路由
SPA
单页面应用,只有一个完整页面,只做局部更新,多组件
路由基本使用:
1.界面中的导航区,展示区
2.导航区的a标签改为Link
<Link to='/xxx'></Link>
3.展示区写Route标签进行路径的匹配
<Route path='/xxx' component={Demo}/>
4.
<App>
最外层包裹
安装:npm i react-router-dom
vue里面是直接叫vue-router
路由:Route
路由器:Router
靠路由链接实现切换组件(编写路由链接):
<Link to="/about"></Link>
注册路由:
<Route path="/about" component={About} />
最好一个页面用一个去包裹,所以经常就是在index.js里渲染的时候 包裹一下
<App/>
注意:
如果直接用Link(或Route)的话 就会出现一个报错(You should not use Link outside a Router)
此时 如果用Router标签去包裹Link 依旧会报错 指向的是Router.js里面的代码
其实 它这个报错信息并不完整 所以造成理解偏差 导致会想着用将Link写到Router标签里面解决
真正的报错是在Router标签
BrowserRouter ///
HashRouter #
要说明是那种Router
路由组件 VS 一般组件:
1.写法不同
一般组件用闭合标签。路由组件用注册路由的方式
2.存放位置不同
一般组件:components. 路由组件:pages
3.接收到的props不同
一般组件:传递了什么 就收到什么
路由组件:接收到路由器传来的3个固定的属性:history,location,match,
路由的严格匹配与模糊匹配:
1.默认使用的是模糊匹配(输入路径必须包含要匹配的路径,且顺序要一致)
2.开启严格匹配exact={true}
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
都匹配不了的时候 一般在最后写个Redirect. 跳转到Redirect指定的路由
向路由组件传递参数:
1.params参数
2.search参数
3.state参数
React-router-dom 相关的API
内置组件
1
2
3
4
5
6
7<BrowserRouter>
HashRouter
Route
Redirect
Link
NavLink
Switch其他
history对象
match对象
withRouter对象
3. Redux
是一个专门用于做状态管理的JS库。可以在react,angular,vue等项目中,但跟react配合较多。因为vue里面也有优秀的处理共享状态的库Vuex
3.1 三个核心概念
action
type:标识属性,值为字符串,唯一,必要属性
data:数据属性,值为类型任意,可选属性
reducer
用于初始化状态,加工状态
加工时,根据旧的state和action 产生新的state的纯函数
store
将state,action,reducer联系在一起的对象
3.2 原理图
- 安装 npm add redux
一个有趣的地方:rendux改变状态之后 不进行render 所以我们要去手动调render
1 | componentDidMount(){ |
但是如何有100个组件,每个组件里面都要写一个componentDidMount去调render吗????
优化:
1 | //在index.js里面进行检测更新 |
但是这样写的话 如果3000个组件中只有一个变化了 其余2999个也会更新
其实没事 因为render有DOM的diff算法
一个小tips:返回函数:()=>({})
action 分为同步 和 异步
同步是 Object。异步是返回function类型的
action只能接受Object的 要是写异步的 需要再写一个中间件
安装redux-thunk:npm add redux-thunk
在store.sj中引入thunk。再从redux中导入applyMiddleware,最后一起暴露出去
4.react-redux
安装:npm add react-redux
connect(mapStateToProps,mapDispatchToProps)(CountUI)
Provider
1
2
3
4
5
6
7RenderDOM.render(
//react-redux 不需要自己去检测更新了 而且通过Provider可以批量为组件传递store
<Provider store={store}>
<Demo1></Demo1>
<Demo2></Demo2>
</Provider>
)汇总reducer 用 combineReducers
redux开发者工具:Redux DevTools
chrome安装插件之后 去vscode安装 npm add redux-devtools-extension
然后去store.js里引入
import { composeWithDevTools } from 'redux-devtools-extension'
5.Hooks
前面提到纯函数组件只能做UI,涉及到状态的话,就只能用类组件或者redux
类组件的缺点:遇到简单的页面,代码就会很重,并且每创建一个类组件,都要去继承一个React实例
至于Redux,很久之前Redux作者就说过,“能用React解决的问题就不用Redux”
React Hooks的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码钩进来
4种常见的钩子:
useState() 状态管理
const [name, setName] = useState(null)
useContext() 共享状态
React16.X以后支持,避免了react逐层通过Props传递数据
const AppContext = React.creactContext({})
const A = () => {
const { name } = useContext(AppContext)
}
const B = () => {
const { name } = useContext(AppContext)
}
return (
<AppContext.Provider value=<!--26-->>
<A/>
<B/>
</AppContext.Provider>
)
useReducer() Action钩子
const [state, dispatch] = useReducer(reducer, initalState)
useEffect() 监听状态变化进行刷新
useEffect(() => {},[array])
第一个参数是要进行异步的操作,第二个参数是一个数组。
只要这个数组发生变化,useEffect就会执行