一、组件类
React的核心是组件, 在v16.8之前,组件的标准写法是类(class)。
以下为一个简单的组件类:
import React, { Component } from 'react'
export default class Click extends Component{
constructor(props){ //如果要使用this.props就必须给super加参数:super(props)
super(props);
this.state={
showText: 'Click me'
}
}
handleClick(){ //button点击事件的方法
this.setState({
showText: 'hello world'
})
}
render(){
return{
<div>
<button onClick=onClick={this.handleClick.bind(this)} >{this.state.showText}</button>
</div>
}
}
}
运行结果:
Reduxde 的作者 Dan Abramov 总结了组件类的几个缺点:
.
1:大型组件很难拆分和重构, 也很难测试。
2:业务逻辑分散在组件的各个方法中, 导致重复逻辑或关联逻辑
3:组件类引入了复杂的编程模式, 比如render、props、高阶组件
名词解读:
1、 export default可用于导出常量、函数、文件、模块等
。 在其他文件中用import将其导入使用。(一个文件只能有一个export default)
2、extends是继承某个类
。继承之后可以使用父类的方法,也可以重写父类的方法
3、constructor()是构造函数
。 通过new命令生成对象实例时自动调用该方法。(该方法是类中必须有的 ==》 如果没有用到constructor可以不写, React会默认添加一个空的constructor)
4、super()是继承
。子类必须在constructor()中调用super()方法(原因:子类并没有自己的this对象, 它只能继承父级类的this对象,然后对其加工。super()就是将父类的this对象继承给子类, 没有super()子类将得不到this对象)
5、render()方法是 接受输入的数据并返回需要展示的内容
二、函数组件
函数组件相比于类组件要简单些。函数组件的定义方式有两种:普通函数、箭头函数
const Hello=(props)=>{
return <div>Hello Word! {props.name}</div>
}
或
function Hello(props){
return <div>Hello Word! {props.name}</div>
}
函数组件不能包含状态state, 也不支持生命周期方法。 必须是一个纯函数
三、Hook的含义
Hook是React16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。Hook是向下兼容
1.无需修改组件结构的情况下, 在组件之间复用状态逻辑
2.将组件中相互关联的部分拆分成更小的函数(比如订阅、请求数据)
3.在非class的情况下可以使用React更多的特性
Hook这个单词的意思就是钩子
组件尽量写成纯函数, 如果需要外部功能和副作用, 就用钩子把外部代码钩进来
以下是React常用的钩子:
四、useState():状态钩子
useState()用于为函数组件引入状态(state), 纯函数是没有状态的,所以把状态放入钩子里面。
函数组件重写上面(一)的组件类:
import React, { useState } from 'react'
export default function Click(){
const [showText, setShowText ] = useState('Click me')
function handleClick(){
return setShowText('hello world')
}
return (
<button onClick={handleClick}>{showText}</button>
)
}
const [showText, setClickText ] = useState('Click me')
第一个成员为变量(showText),第二成员个为函数是用来更新状态,约定命名方式为set+状态名(setShowText)。'Click me'
为showText变量的默认值。
运行结果:
1、state为一个对象时
当
state
是一个对象时,不可部分更新属性。因为setState
不会帮我们合并属性。
代码举例:setState修改用户的姓名,其他属性不会被合并
import React,{ useState } from 'react'
export default function ChangeName(){
const [showText, setShowText] = useState({name:'guoguo', age:'18'})
function handleClick(){
return setShowText({ //setState不会帮我们合并属性
name: 'saisai'
})
}
return(
<div>
<div>姓名:{showText.name}</div>
<div>年龄:{showText.age}</div>
<button onClick = { handleClick } >change Name</button>
</div>
)
}
运行结果:
function handleClick(){
return setShowText({
...showText, // 拷贝之前的所有属性
name: 'saisai' // 这里的name覆盖之前的name
})
}
修改useState后的运行结果:
五、useEffect(): 副作用钩子
useEffect()
用来具有副作用的操作, 最常见的就是向服务器请求数据。以前放在componentDidMount()
里面的代码,现在可以放在useEffect()
中。useEffect在浏览器渲染完成后执行
useEffect()的用法如下:
useEffect(()=>{
// 异步函数体
}, [dependencies])
上面方法中,useEffect()
接受两个参数, 第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出Effect的依赖项,只要这个数组发生变化,useEffect()
就会执行。第二个参数可以省略, 这时组件每次渲染时就会执行useEffect()
import React, { useEffect, useState } from 'react'
export default function effectFun(){
const nameLists = ['guoguo', 'saisai', 'lulu', 'meimei']
const [name, setName] = useState('guoguo')
function changeName(){
console.log('changeName触发')
return name
}
useEffect(()=>{
console.log('name改变的时候触发')
}, [name])
return(
<div>
<p>{changeName()}</p> //调用方法
<button onClick={()=>setName(nameLists[Math.random()*nameLists.length << 0])}>修改名称</button>
</div>
)
}
运行结果:
- 点击按钮使用
setName
的时候, 产生DOM
操作,刷新页面DOM
的同时也触发了p
标签中的changeName
函数 - 然后调用副作用触发了
name
的effect
以上1、2为第一次渲染页面的时候运行changeName()、useEffect()方法打印出的结果。3、4为点击“修改名称”按钮后打印的结果。
运行结果1、2说明:页面每次重新渲染时就会执行useEffect()。运行结果3、4说明:当调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数
1、useLayoutEffect布局副作用
useEffect
在浏览器渲染完成后执行
useLayoutEffect
在浏览器渲染前执行 (比useEffect
先执行)
六、useContext(): 共享状态钩子
useContext()
组件之间共享状态钩子
useContext()的使用方法请看下面的例子:
import React, { createContext, useContext } from 'react'
const Context = createContext({}) //在组件外建立一个Context对象, 这个对象可以被子组件共享
function Guoguo(){
const { name } = useContext(Context) // 共享父级的参数
return (
<div>
<div>guoguo</div>
<div>{name}</div>
</div>
)
}
function Saisai(){
const { name } = useContext(Context) // 共享父级的参数
return (
<div>
<div>saisai</div>
<div>{name}</div>
</div>
)
}
export default function Family(){ // 父级组件
return(
<Context.Provider value={{ //提供给子组件使用的值
name: 'happy'
}}>
<div>
<Guoguo/> //函数组件
<Saisai/>
</div>
</Context.Provider>
)
}
运行结果
七、useReducer(): action钩子
useReducer()钩子用来引入Reducer功能
-
React
本身不提供状态管理功能,通常需要使用外部库。这方面常用的库是Redux
、Mobx
-
Redux
的核心概念是 组件发出action
后与状态管理器通信。状态管理器接收到action
后,使用Reducer
函数算出新的状态,Reducer
函数的形式是(state, action) => newState
useReducer的用法举例:
import React, { useReducer } from 'react'
const myReducer = (state, action) => { // useReducer不会对属性进行合并
if(action.type === '0') {
return{
...state, // 拷贝所有的状态属性
count: state.count + 1 // 覆盖属性中的count字段
}
} else {
return state
}
}
export default function reducerFun(){
const [state, dispatch] = useReducer(myReducer, {count: 0})
return (
<div>
<div>{state.count}</div>
<button onClick={()=>dispatch({type: '0'})}>+1</button>
</div>
)
}
运行结果:
解读:
useReducer ()
它接Reducer
函数和状态的初始值作为参数, 返回一个数组。数组的第一个成员是当前的状态值, 第二个成员是发送action
的dispatch
函数。- 以上
myReducer()
接收状态值
和dispatch函数要发送的action
作为参数, 返回一个新状态state
useReducer与useState类似, 都不会合并属性
1、Redux中的名词解读:
store
:保存数据的地方,可以把它看成一个容器,整个应用只能有一个Store。state
:store对象中包含所有的数据,如果想得到某个时点的数据,就要对store生成快照。这种时点的数据集合就叫state注:redux规定一个state对应一个view,只要state相同,View就相同
action
:view视图发出的通知,表示state应该要发生变化(view要发送多少种通知就会有多少种action)action Creator
:用来生成action的函数store.dispatch()
: view发出action的唯一方法reducer
:store收到action以后,必须给出一个新的state,这样view才会发生变化,这种state的计算过程就叫做reducerstore.subscribe()
:设置监听函数,一旦state发生变化,就自动执行这个函数
八、useMemo()
传入
useMemo
的函数会在渲染期间
执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect
的适用范畴,而不是useMemo
useMemo() 用法举例:
import React, { useEffect, useState, useMemo } from 'react'
export default function effectFun() {
const nameLists = ['guoguo', 'saisai', 'lulu', 'meimei']
const [name, setName] = useState('guoguo')
const [age, setAge] = useState(18)
function changeName() {
console.log('changeName触发')
return name
}
useEffect(() => {
console.log('name effect改变的时候触发')
}, [name])
const memoFun = useMemo(()=>{
console.log('name memo改变的时候触发')
return () => name // 返回一个函数
}, [name])
return (
<div>
<div>普通方法{changeName()}</div>
<div>memo方法{memoFun()}</div>
<button onClick={() => setName(nameLists[Math.random() * nameLists.length << 0])}>修改名称</button>
<button onClick={() => setAge(age + 1)}>修改年龄</button>
</div>
)
}
运行结果:
-
运行结果1
:渲染页面的打印结果useMemo是在页面渲染期间执行
-
运行结果2
:点击“修改名称”按钮的打印结果 -
运行结果3
:点击“修改年龄”按钮的打印结果
将以上代码中的memoFun()修改如下:
const memoFun = useMemo(()=>{
setName({
name: name[Math.random() * nameLists.length << 0]
})
console.log('name memo改变的时候触发')
return () => name
}, [name])
运行结果:
useMemo
在DOM
改变的时候,可以控制某些函数不被触发点击“修改年龄”按钮时memoFun()没有执行
useMemo
中不可使用setState
, 不然会造成死循环并有警告。useMemo是在渲染中进行的,如果在其中操作DOM后,又会导致memo触发
memo
是在DOM
更新前触发的,就像官方所说的,类比生命周期的shouldComponentUpdateuseMemo运行后返回的是一个函数
1、useCallback
- 函数式组件每次任何一个
state
的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。useCallback(fn, deps)
相当于useMemo(() => fn, deps)
。
function showName(name){
console.log(name + '改变的时候触发')
return () => name
}
// ---------------------------------------------------------
const memoFun = useMemo(()=>showName('memo'), [name])
// 等价于
const callBackFun = useCallback(showName('callBack'), [name])
八、useRef()
可以用来引入
DOM对象
或`普通对象
useRef用法如下:
const refContainer = useRef(initialValue)
命令式地访问子组件:
-
useRef()
返回一个可变的ref
, 其.current
属性被初始化为传入的参数(initialValue)。返回的ref
对象在组件的整个生命周期保持不变
import React, { useRef } from 'react'
export default function getFoucsInput(){
const inputRef = useRef(null)
const buttonFocus = ()=>{ //current已经挂载到DOM上的文本输入元素
console.log(inputRef)
inputRef.current.focus()
inputRef.current.value = '123' // console.log(inputRef.current.value) 控制台输出为: 123
}
const showText = ()=>{
console.log('current变化时,是否渲染DOM')
}
return(
<div>
<div>{showText()}</div>
<Input value='默认value'></Input>
<button onClick={buttonFocus}>input获取焦点</button>
</div>
)
}
运行结果:
- 当
ref
对象内容发生变化时,useRef
并不会通知你。- 变更
.current
属性不会引发组件重新渲染 。点击按钮 “input获取焦点” 后 showText() 方法没有执行
1、props无法传递ref属性
import React, { useRef } from 'react'
export default function inputCom(){
const inputRef = useRef(null)
return(
<div>
<Input ref={inputRef} value='123'></Input>
</div>
)
}
const Input = (props, ref) => {
console.log(props, ref)
return <input {...props} />
}
运行结果:
props
不包含ref
。 实现ref
的传递需要用到forwardRef
2、forwardRef: 传递ref属性
export default function inputCom(){
const inputRef = useRef({})
return(
<div>
<Input ref={inputRef} value='123'></Input>
</div>
)
}
const Input = forwardRef((props, ref) => {
console.log(props, ref)
return <input ref={ref} {...props} />
})
运行结果:
九、自定义Hook
input
输入框在change
的时候输出input的值
以及 input的值+值形式
例子如下:
import React, { useState, useRef } from 'react'
const useNumber = (name)=>{ //自定义Hook
const setName = (newName)=>{
if (newName){
return `${newName} + ${newName}`
}
}
return [name.n, setName] //返回input的值 以及 方法
}
const NumberCom = (n)=>{ //函数组件
const [name, setName] = useNumber(n)
return(
<div>
<div>输入框的值:{name}</div>
<div>输入框的值+值:{setName(name)}</div>
</div>
)
}
export default function myHook(){
const [inputVal, setInputVal] = useState()
const inputRef = useRef(null)
const changeName = () => {
setInputVal(inputRef.current.value) //更新input的值
}
return (
<div>
<input ref={inputRef} onChange={changeName} placeholder='请输入...'></input>
<NumberCom n={inputVal}></NumberCom>
</div>
)
}
运行结果:
useNumber()
为自定义的Hook。返回两个成员,第一个成员是input的值
,第二个成员是一个方法
NumberCom()
是一个函数组件。参数n的结构为{n: "122"}
十、结束
仅供自己参考, 有问题请留言
巴拉拉小魔仙, 变身 ......