react函数式编程快速上手

216 阅读6分钟

项目搭建

关键步骤

  1. 打开终端 或 powershell窗口
  2. 输出npx create-react-app [项目名]
  3. cd项目名进入项目目录
  4. npm start启动项目
  5. 浏览器中查看

JSX

一种结合了JavaScript和html的模板语法

打开App.js

import logo from './logo.svg';
import './App.css';
​
function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  );
}
​
export default App;
  1. return后的括号,在jsx需要换行的时候 是必须的,建议无论多行还是单行都使用
  2. JSX只能返回单个根元素,可以使用 空标签 <> </>的方式设置根元素,也可以用react提供的fragment标签
  3. JSX和Html一样 必须要标签闭合

数据渲染

  1. 插值表达式,可以使用大括号中包含变量的方式书写

    1. 标签内容
    2. 标签属性
  2. 条件渲染

    function App() {
      const divTitle = '标题'
      const flag = false
      let divContent = null
      if(flag){
        divContent = <span>flag为true</span>
      }else{
        divContent = <p>flag为false</p>
      }
      return (
        <div title={divTitle}>
          {divContent}
        </div>
      );
    }
    ​
    export default App;
    ​
    
  3. 循环渲染,需要设置key属性(react用此保证性能)

    function App() {
      const list = [
        {id:1,label:'a'},
        {id:2,label:'b'},
        {id:3,label:'c'}
      ]
      const listContent = list.map(item => {
        <li key={item.id}>{item.label}</li>
      })
      return (
        <ul>
          {listContent}
        </ul>
      );
    }
    ​
    export default App;
    

事件处理

react将事件处理属性的名称改为驼峰的形式

function App() {
  
  function handlerClick(e){
    console.log('点击了按钮',e);
  }
​
  return (
    <button onClick={handlerClick}>按钮</button>
  );
}
​
export default App;

状态处理

使用useState来处理,他的set操作,是完全覆盖

import './App.css';
import { Fragment, useState } from 'react';
​
function App() {
  const [content,setContent] = useState('标签的默认内容')
  function handlerClick(){
    console.log('点击了按钮');
    setContent('新内容')
  }
​
  return (
    <Fragment>
      <div>{content}</div>
      <button onClick={handlerClick}>按钮</button>
    </Fragment>
  );
}
​
export default App;

组件传值

组件在标签行内通过{[变量]}的方式进行传值 其中class属性更改为className style属性需要通过传递一个对象的方式进行处理

1.常见属性传值

import image from './logo.svg'
import './App.css';
​
function App() {
  const imgStyleObj = {
    width:200,
    height:200,
    backgroundColor:'gery'
  }
  return (
    <div>
      <img 
      src={image} 
      alt="" 
      className='small'
      style={imgStyleObj}
      />
    </div>
  );
}
​
export default App;
​

2.展开语法传值

使用展开语法对属性进行传值,这是jsx的特定写法,不是es6的展开运算符写法

import image from './logo.svg'
import './App.css';
​
function App() {
  const imgData = {
    className:'small',
    style:{
      width:200,
      height:200,
      backgroundColor:'gery'
    },
  }
  return (
    <div>
      <img src={image} alt="" {...imgData} />
    </div>
  );
}
​
export default App;

3.父向子传值

父向子传值,props传值,这是一种单项的数据传递

function Article(props){
  return (
    <div>
      <h2>{props.title}</h2>
      <p>{props.content}</p>
    </div>
  )
}
​
function App() {
  const imgData = {
    className:'small',
    style:{
      width:200,
      height:200,
      backgroundColor:'gery'
    },
  }
  return (
    <>
    <Article title="标签1" content="内容1"/>
    <Article title="标签2" content="内容2"/>
    <Article title="标签3" content="内容3"/>
    </>
  );
}
​
export default App;
​

将jsx作为porps传递(组件插槽),在porps中使用children读取

function List ({children,title,footer = <div>默认底部</div> }){
  return(
    <>
      <h2>{title}</h2>
      <ul>
      {children}
      </ul>
      {footer}
    </> 
  )
}
export default function App() {
​
  return (
    <>
      <List title="列表1" footer={<p>这是底部1</p>}>
        <li>列表项</li>
        <li>列表项</li>
        <li>列表项</li>
      </List>
    </>
  );
}
​
​

4.子向父传值

通过父组件向子组件传递回调函数的形式,进行传值(可以理解为父组件必须向子组件授权修改方式)

import { useState } from "react";
​
function Detail ({onStatuesChange}){
  const [isHide,setIsHide] = useState(false)
  const handlerClick = ()=> {
    const cur = !isHide
    setIsHide(cur)
    onStatuesChange(cur)
  }
  return (
    <div>
      <p style={{display:isHide?'none':'block'}}>内容</p>
      <button onClick={handlerClick}>按钮</button>
    </div>
  )
}
export default function App() {
  const handlerStatuesChange = (value) => {
    console.log(value);
  }
  return (
    <>
      <Detail onStatuesChange={handlerStatuesChange}/>
    </>
  );
}
​

5.兄弟组件传值

  1. 使用createContext创建一个共享的值
  2. 使用useContext来获取共享的值
  3. 当需要在局部修改这个值的时候 使用<LevelContext.Provider value={}> 方式修改
import { createContext, useContext } from "react";
​
const LevelContext = createContext(0)
​
function Section({ children }) {
  const level = useContext(LevelContext)
  console.log(level);
  return (
    <section>
      <LevelContext.Provider value={level + 1}>
        {children}
      </LevelContext.Provider>
    </section>
  )
}
function Heading({ children }) {
  const level = useContext(LevelContext)
  switch (level) {
    case 1: return <h1>{children}</h1>
    case 2: return <h2>{children}</h2>
    case 3: return <h3>{children}</h3>
    case 4: return <h4>{children}</h4>
    case 5: return <h5>{children}</h5>
    case 6: return <h6>{children}</h6>
  }
}
​
export default function App() {
  return (
    <div>
      <Section>
        <Heading>主标题</Heading>
        <Section>
          <Heading>副标题</Heading>
          <Heading>副标题</Heading>
          <Heading>副标题</Heading>
          <Section>
            <Heading>副标题2</Heading>
            <Heading>副标题3</Heading>
            <Heading>副标题4</Heading>
          </Section>
        </Section>
      </Section>
    </div>
  )
}

常见勾子函数

1.useReducer

useReducer 是 React 中的一个 Hook,它是 useState 的替代方案,用于管理复杂的状态逻辑。

import React, { useReducer } from 'react';
​
// 定义 reducer 函数
// reducer 接收两个参数:当前状态和动作
const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            return state;
    }
};
​
// 定义初始状态
const initialState = { count: 0 };
​
const Counter = () => {
    // 使用 useReducer
    const [state, dispatch] = useReducer(reducer, initialState);
​
    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
        </div>
    );
};
​
export default Counter;

2.useRef

useRef可以用来保存非响应式的值,更改其保存的current的值 不会引起页面的响应式的更新 一下例子,p标签中的值在页面上始终是0

import React, { useRef, useEffect } from 'react';
​
const Counter = () => {
    const countRef = useRef(0);
​
    useEffect(() => {
        const intervalId = setInterval(() => {
            // 更新 ref 的值
            countRef.current++;
            console.log('Count:', countRef.current);
        }, 1000);
​
        return () => {
            clearInterval(intervalId);
        };
    }, []);
​
    return (
        <div>
            <p>Count in ref: {countRef.current}</p>
        </div>
    );
};
​
export default Counter;

useRef还可以用来访问DOM元素,直接获取元素的信息或调用元素的方法

import React, { useRef } from 'react';
​
const TextInput = () => {
    const inputRef = useRef(null);
​
    const focusInput = () => {
        // 通过 ref 访问 DOM 元素并调用 focus 方法
        inputRef.current.focus();
    };
​
    return (
        <div>
            <input ref={inputRef} type="text" placeholder="Enter text" />
            <button onClick={focusInput}>Focus Input</button>
        </div>
    );
};
​
export default TextInput;

获取子组件(需要配合fowardRef使用)

import React, { forwardRef, useImperativeHandle, useRef } from 'react';
​
const Child = forwardRef(function (props, ref) {
  useImperativeHandle(ref, () => {
    return {
      //暴露给父组件的方法
      myFn: () => {
        console.log('子组件myfn');
      }
    }
  })
​
  return (
    <div>子组件</div>
  )
})
​
const TextInput = () => {
  const childRef = useRef(null);
  function handlerClick() {
    childRef.current.myFn()
  }
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={handlerClick}>按钮</button>
    </div>
  );
};
​
export default TextInput;

3.useEffect

在组件加载或更新的时候,做一些额外的操作的钩子

useEffect接受两个参数,一个参数是执行的回调函数,一个参数是监听更新的数组,若没有第二个参数则组件的任何状态更新都会触发回调函数

import React, { useEffect, useReducer } from 'react';
​
// 定义 reducer 函数
// reducer 接收两个参数:当前状态和动作
const reducer = (state, action) => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            return state;
    }
};
​
// 定义初始状态
const initialState = { count: 0 };
​
const Counter = () => {
    // 使用 useReducer
    const [state, dispatch] = useReducer(reducer, initialState);
​
    useEffect(()=>{
      console.log('useEffect');
    },[state.count]) 
    //组件第一次加载的时候无论什么情况都会执行回调函数
    //若第二个参数为空,那么组件在状态发生任何变化的时候都会执行回调函数
    //若这里第二个参数为一个空的数组,那么只会在第一次加载的时候执行回调函数
    //若这里第二个参数的数组不为空,那么除了第一次加载还会监听其内部的值发生变化的时候执行回调函数
    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
            <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
        </div>
    );
};
​
export default Counter;

4.useMemo

主要用于性能优化,它的核心作用是对一些计算量较大的操作进行缓存,避免在每次组件渲染时都重新计算,从而提高组件的性能。

import React, { useMemo, useState } from 'react';
​
const ExpensiveCalculation = ({ num }) => {
  // 模拟一个复杂的计算
  const calculateSum = () => {
    console.log('计算触发');
    let sum = 0;
    for (let i = 0; i < num; i++) {
      sum += i;
    }
    return sum;
  };
​
  // 使用 useMemo 缓存计算结果
  const sum = useMemo(() => calculateSum(), [num]);
​
  return (
    <div>
      <p>计算从0到 {num - 1} 的合是 {sum}</p>
    </div>
  );
};
​
const App = () => {
  const [data, setData] = useState(1)
  const [number, setNumber] = useState(10);
​
  const handleIncrementData = () => {
    setData(data => data + 1)
  }
​
  const handleIncrement = () => {
    setNumber(prevNumber => prevNumber + 1);
  };
​
  return (
    <div>
      <p>无关数据{data}</p>
      <button onClick={handleIncrementData}>增加无关数据</button>
      <button onClick={handleIncrement}>增加计算数据</button>
      <ExpensiveCalculation num={number} />
    </div>
  );
};
​
export default App;

memo组件

使用memo函数包裹组件,组件的props没有发生变化的时候,自身不会触发重新渲染

import React from 'react';
​
// 定义一个普通的函数组件
const MyComponent = (props) => {
    console.log('我渲染了');
    return <div>{props.message}</div>;
};
​
// 使用 React.memo 包裹组件
const MemoizedComponent = React.memo(MyComponent);
​
const App = () => {
    const [count, setCount] = React.useState(0);
​
    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            {/* 使用被 memo 包裹后的组件 */}
            <MemoizedComponent message="Hello, World!" />
        </div>
    );
};
​
export default App;

5.useCallBack

在 React 中,函数组件每次渲染时,组件内定义的函数都会被重新创建,这意味着函数的引用会发生变化。如果将这些函数作为 props 传递给子组件,可能会导致子组件不必要的重新渲染,尤其是当子组件使用了 React.memo 进行优化时

import React, { useCallback, useState } from 'react';
​
const ChildComponent = React.memo(({ onClick }) => {
    console.log('我渲染了');
    return <button onClick={onClick}>Click me</button>;
});
​
const ParentComponent = () => {
    const [count, setCount] = useState(0);
​
    // 使用 useCallback 缓存回调函数 
    // 如果这里不使用useCallback包裹的化,每次重新渲染的时候给子组件的都会是一个新的函数,从而导致子组件即使使用了memo也会触发重新渲染
    const handleClick = useCallback(() => {
        setCount(prevCount => prevCount + 1);
    }, []);
​
    return (
        <div>
            <p>Count: {count}</p>
            <ChildComponent onClick={handleClick} />
        </div>
    );
};
​
export default ParentComponent;