react—Day2

136 阅读7分钟

受控表单绑定

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>

1708328799495.png

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>
  );
}