受控表单绑定
vue中通过v-model来实现数据和视图的双向数据绑定,react如何实现数据和视图的双向绑定呢。
概念:使用react组件的状态(useState)控制表单的状态
// 1.声名一个状态useState
// 2.核心绑定流程
// 3.通value属性绑定状态
// 4.绑定onChange事件,通过参数e.target.value获取最新的值反向修改状态
const [num,setNum] = useState(100)
<input
value={num}
onChange={(e)=>{setNum(e.target.value)}}
></input>
react中获取DOM
在react组件中获取/操作DOM,需要使用useRef钩子函数。
分两步: 1:使用useRef创建ref对象
const inpDom = useRef(null);
并且与JSX绑定
<input type='text' ref={inpDom}></input>
2:通过inpDom.current拿到Dom
console.log(inpDom.current);
组件通信
父子通信
1:父组件传递数据——在子组件上绑定属性
2:子组件接收数据——子组件通过propos参数接收数据
父
function App() {
const num = 'tsy';
return (
<div className="App">
<Son name = {num}></Son>
</div>
);
}
子
function Son(props) {
console.log(props);
return (
<div>{props.name}</div>
)
}
props可传递任意的数据 数字,字符串,布尔值,数组,对象,函数,JSX
<Son
name = {'tsy'}
age = {25}
isNan = {true}
list = {['vue','react']}
obj = {{id:1,title:'title'}}
cd = {()=>{console.log('cd')}}
child = {<span>span</span>}
></Son>
props是只读对象 子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能父组件修改。
父传子特殊的prop children 当把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容 (类似于vue的插槽)
<Son>
<span>插槽</span>
</Son>
function Son(props) {
return (
<div>sss{props.children}</div>
)
}
子传父
子传父核心思想:在子组件中调用父组件中的函数并且传递参数
//子组件
function Son(props) {
const [num,setNum] = useState('151524');
return (
<div>
<button onClick={()=>{props.cd(num)}}>+</button>
</div>
)
}
//父组件
function App() {
const [numA,setNumA] = useState(null);
const addnum = (id)=>{
setNumA(id)
}
return (
<div className="App">
<div>{numA}</div>
<Son cd = {addnum}></Son>
</div>
);
}
状态提升实现兄弟组件的通信
思路:借助“状态提升”机制,通过共同的的父组件进行兄弟组件之间的数据通信
1:先通过子传父,将组件A数据传给父组件
2:父组件拿到数据后,在通过父传子,传给组件B
function SonA(props) {
return (
<div>
<div>{props.str}</div>
</div>
)
}
function SonB(props) {
const str = 'this is string'
return (
<div>
<button onClick={()=>{props.cd(str)}}>-</button>
</div>
)
}
function App() {
const [str,setStr] = useState('sss');
const getstr = (data)=>{
setStr(data)
}
return (
<div className="App">
<div>父:{str}</div>
<SonB cd = {getstr}></SonB>
<SonA str = {str}></SonA>
</div>
);
}
使用Context机制跨层级组件通信
只要组件和组件之间存在嵌套关系就可以使用
实现步骤:
1:使用createContext方法创建一个上下文对象Ctx
2:在顶层组件(App)中通过Ctx.Provider组件提供数据
3:在底层组件(B)中通过useContext钩子函数获取数据
// 步骤一:使用createContext方法创建一个MyCtx对象
const MyCtx = createContext();
function App() {
const str = 'this is string'
return (
<div className="App">
{/* 步骤二:在最外层使用MyCtx对象的Provider组件,并且使用value属性传递所需要的值 */}
<MyCtx.Provider value={str}>
<SonA></SonA>
</MyCtx.Provider>
</div>
);
}
function SonA(props) {
return (
<div>
A
<SonB></SonB>
</div>
)
}
function SonB(props) {
// 步骤三:在底层组件中使用useContext方法接收
const bstr = useContext(MyCtx)
return (
<div>
B {bstr}
</div>
)
}
插槽
利用组件的 props.children 可将插槽内容渲染出来
父组件 在子组件标签中填入结构
import Son from './son'
function father() {
return (
<div>
<span>father</span>
<Son>
<span>插槽内容</span>
</Son>
</div>
)
}
子组件用children接收
function son({children}) {
return (
<div>
<span>son</span>
{children}
</div>
)
}
mome 跳过重新渲染
在react中,默认情况下,当一个组件被重新渲染时,react将递归渲染它所有的子组件。
列如:当父组件中的numA和num每次改变时,则父组件将会重新渲染,则子组件也会被重新渲染。 这样会导致大量重排重绘,浪费性能。
父组件
function Father({numA}) {
console.log('父组件渲染');
const [num,setNum] = useState(0)
return (
<div>
<span>father{num}</span>
<div>{numA}</div>
<div>
<button onClick={()=>setNum(num+1)}>+</button>
</div>
<Son></Son>
</div>
)
}
子组件
function Son() {
console.log('子组件渲染');
return (
<div>
<span>son</span>
</div>
)
}
优化:用memo保住子组件,则在父组件重新渲染时,子组件不会被递归渲染
const Son = memo(function Son() {
console.log('子组件渲染');
return (
<div>
<span>son</span>
</div>
)
})
export default Son
useCallback:在多次渲染中缓存函数
当子组件被mome包住后不会受到父组件重新渲染而重新渲染的影响,如果将一个函数fun传入子组件,则父组件的更新会影响fun函数的更新,fun函数的更新会影响到子组件中的props更新,子组件中的props.fun更新会导致子组件重新渲染。那么mome将缓存不住子组件。此时就要借助useCallback将fun缓存住来防止子组件中props.fun更新。
//父组件
function Father({numA}) {
const [num,setNum] = useState(0);
const fun = ()=>{
axios.get('http://geek.itheima.net/v1_0/channels').then((res)=>{
console.log(res);
})
}
return (
<div>
<span>father{num}</span>
<div>{numA}</div>
<div>
<button onClick={()=>setNum(num+1)}>+</button>
</div>
<Son fun={fun}></Son>
</div>
)
}
//子组件
const Son = memo(function Son({fun}) {
fun()
return (
<div>
<span>son</span>
</div>
)
})
优化:useCallback缓存fun,只有在依赖项改变时,才不会缓存函数。
function Father({numA}) {
const [num,setNum] = useState(0);
const fun = useCallback(()=>{
axios.get('http://geek.itheima.net/v1_0/channels').then((res)=>{
console.log(res);
})
},[numA]) //只要这些依赖没有改变
return (
<div>
<span>father{num}</span>
<div>{numA}</div>
<div>
<button onClick={()=>setNum(num+1)}>+</button>
</div>
{/* Son 就会收到同样的 props 并且跳过重新渲染 */}
<Son fun={fun}></Son>
</div>
)
}
注意:useCallback只在顶层组件中使用
useEffect
useEffect是一个react Hook函数,用于在react组件中创建不是由事件引起,而是由渲染本身引起的操作,比如发生ajax请求,更改DOM等。
//语法:useEffect(()=>{},[])
//参数一:是个函数,也可以叫做副作用函数,在函数内部可以放置想执行的操作
//参数二(可选):在数组里放置依赖项,不同的依赖项会影响第一个参数函数的执行,
//当是一个空数组时候,副作用函数只会在组件渲染完毕之后执行一次
const URL = '请求地址'
function App() {
const [listarr,setListarr] = useState([])
useEffect(()=>{
async function getlist (){
let res = await fetch(URL);
let {data} = await res.json()
setListarr([...data.channels])
}
getlist()
},[])
return (
<div className="App">
{
listarr.map(item=> <div key={item.id}>{item.name}</div> )
}
</div>
);
}
依赖项参数说明
useEffect副作用函数的执行时机存在多种情况,根据传入的依赖项不同,会有不同的表现。
| 依赖项 | 副作用函数执行时机 |
|---|---|
| 没有依赖项 | 组件初始渲染 + 组件更新时执行 |
| 空依赖项 | 只在初始渲染时执行一次 |
| 添加特点的依赖项 | 组件初始渲染 + 特性依赖项变化时执行 |
情况一
function App() {
// 组件更新渲染执行一次
const [num,setNum] = useState(0)
useEffect(()=>{
// 组件初始化渲染执行一次
console.log('没有依赖项');
})
return (
<div className="App">
<div>{num}</div>
<button onClick={()=>{setNum(8)}}>+</button>
</div>
);
}
情况二
const [num,setNum] = useState(0)
useEffect(()=>{
// 组件初始化渲染执行一次,组件更新不执行
console.log('没有依赖项');
},[])
return (
<div className="App">
<div>{num}</div>
<button onClick={()=>{setNum(8)}}>+</button>
</div>
);
情况三
function App() {
// 组件更新numA时,useEffect不执行
const [numA,setNumA] = useState(0)
// 组件更新numB时,useEffect执行一次
const [numB,setNumB] = useState(0)
useEffect(()=>{
// 组件初始化渲染执行一次
console.log('没有依赖项');
},[numB])//将numB添加为依赖项
return (
<div className="App">
<div>{numA}</div>
<div>{numB}</div>
<button onClick={()=>{setNumA(8)}}>+</button>
<button onClick={()=>{setNumB(9)}}>+</button>
</div>
);
useEffect清除副作用
在useEffect中编写的由渲染本身引起的对接组件外部的操作,把他叫做副作用操作。
比如useEffect中开启了一个定时器,想在组件卸载的时候将这个定时器清除掉,这个过程就叫做清除副作用(定时器不及时清理会出现内存泄漏)
useEffect(()=>{
// 实现副作用操作逻辑
return ()=>{
// 清除副作用逻辑
}
},[])
说明:清除副作用的函数最常见的执行时机是在组件卸载时自动执行
实例
function Son (){
// 渲染时开启一个定时器
useEffect(()=>{
const time = setInterval(()=>{
console.log('执行了');
},1000)
return ()=>{
// 清除副作用(组件卸载时)
clearInterval(time)
}
},[])
return (
<div>son</div>
)
}
function App() {
// 通过条件渲染模拟组件卸载
const [isShow,setIsshow] = useState(true)
return (
<div className="App">
{isShow && <Son></Son>}
<div>app</div>
<button onClick={()=>setIsshow(false)}>+</button>
</div>
);
}
自定义Hook函数
自定义Hook是以use打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
封装Hook的思路
第一步:声明一个以use打头的函数。
第二步:在函数体内封装可复用的逻辑(只要是可复用的逻辑都可以)
第三步:将组件中用到的状态或者回调return出去(以对象或者数组形式)
第四步:在哪个组件中使用这个逻辑,就执行这个函数,在解构出状态和回调使用
例子 如何将以下代码封装成Hook函数
function App() {
const [isSh,setIssh]=useState(true)
const taggle = ()=> setIssh(!isSh)
return (
<div className="App">
{isSh && <div>this is app</div>}
<button onClick={taggle}>taggle</button>
</div>
);
}
封装后
function useTaggle(){
const [isSh,setIssh]=useState(true)
const taggle = ()=> setIssh(!isSh)
return {
isSh,
taggle
}
}
function App() {
const {isSh,taggle} = useTaggle()
return (
<div className="App">
{isSh && <div>this is app</div>}
<button onClick={taggle}>taggle</button>
</div>
);
}
react Hook使用规则
1:只能在组件中或者在其他hook函数中调用
//不能在组件外使用
const {isSh,taggle} = useTaggle()
function App() {
return (
<div className="App">
{isSh && <div>this is app</div>}
<button onClick={taggle}>taggle</button>
</div>
);
}
2:只能在组件顶层使用,不能嵌套在if,for,其他函数中
function App() {
// 不能嵌套在if,for,其他函数中
if(Math.random()>0.5){
const {isSh,taggle} = useTaggle()
}
return (
<div className="App">
{isSh && <div>this is app</div>}
<button onClick={taggle}>taggle</button>
</div>
);
}