React小结

React是一个用于构建用户界面的 JavaScript 库,具有如下特点:

  • a、声明式设计
    react是面向数据编程,不需要直接去控制dom,你只要把数据操作好,react自己会去帮你操作dom,可以节省很多操作dom的代码。这就是声明式开发。
  • b、使用JSX语法
    JSX 是 JavaScript 语法的扩展。React 开发大部分使用 JSX 语法(在JSX中可以将HTML于JS混写)。
  • c、灵活
    react所控制的dom就是id为root的dom,页面上的其他dom元素你页可以使用jq等其他框架 。可以和其他库并存。
  • d、使用虚拟DOM、高效
    虚拟DOM其实质是一个JavaScript对象,当数据和状态发生了变化,都会被自动高效的同步到虚拟DOM中,最后再将仅变化的部分同步到DOM中(不需要整个DOM树重新渲染)。
  • e、组件化开发
    通过 React 构建组件,使得代码更加容易得到复用和维护,能够很好的应用在大项目的开发中。
  • f、单向数据流
    react是单向数据流,父组件传递给子组件的数据,子组件能够使用,但是不能直接通过this.props修改。 这样让数据清晰代码容易维护。
    React的基本使用
    1.使用 create-react-app 脚手架创建项目
    npx create-react-app 项目名 或者 yarn create react-app 项目名(npx 是一个临时使用第三方模块的命令,会临时下载这个包,使用完毕就删除了)
    2.项目结构

3.启动项目
我们只需要在项目根目录下使用 npm start 或者 yarn start 就可以启动项目。

react17的ReactDOM.render()用法

import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render((
  <h1>Hello World</h1>
),
  document.getElementById('root')
)

react18的ReactDOM.createRoot的用法。

import React from 'react';
import ReactDOM from 'react-dom/client';
const rootEl = document.getElementById('root')
const root = ReactDOM.createRoot(rootEl)
root.render(<h1>Hello World</h1>)

JSX语法
1.了解JSX
JSX其实就是在JS里面写XML(HTML),在React里面使用JSX来代替常规的JS。

我们不需要一定使用 JSX,但它有以下优点:

JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
它是类型安全的,在编译过程中就能发现错误。
使用 JSX 编写模板更加简单快速。
通常来说,在React项目里面,无论是.js文件还是.jsx文件,都可以写JSX语法。也就是说无论是.js还是.jsx都是React的组件,所以当我们创建一个组件文件时,写.js或者.jsx都是可以的。但是推荐使用.jsx,这样可以更加明确这是一个jsx的组件。

  • 1.在jsx里面可以html和js混写

  • 2.在js里面写html要加小括号

  • 3.在html里面写js要加大括号

  • 4.js/jsx/tsx文件里面都可以写jsx语法
    1.class组件
    在早期的react用法里面,主要是class组件,但是因为这种方式量级比较重,以及使用起来比较繁琐,后期维护难度比较高,所以在后期的项目中已经基本不使用了。但是我们还是要知道这种写法。
    class组件需要我们继承自react提供的一个Component类,并在里面提供一个render方法,来返回一个jsx结构。
    定义一个class组件的基本格式如下。

import { Component } from "react";

export default class ComClass extends Component {
  render() {
    return <div>ComClass</div>;
  }
}

2.函数式组件
react16.8开始,因为hook的推出,极大地提升了函数式组件的实用性,所以基本在外面的项目中,都全面转型到了 FC + Hook 的用法。
函数式组件只有一个函数,需要返回一个jsx结构。相比于class组件而言,非常地轻量。
定义一个函数组件的基本格式如下:

export default function ComFun() {
  return <div>ComFun</div>;
}

生命周期
class组件里面的9个生命周期函数。
React的生命周期指从组件被创建到销毁的过程。掌握了组件的生命周期,就可以在适当的时候去做一些事情。
React生命周期可以分成三个阶段:

  • 1、实例化(挂载阶段):对象创建到完全渲染

  • 2、存在期(更新期):组件状态的改变

  • 3、销毁/清除期:组件使用完毕后,或者不需要存在与页面中,那么将组件移除,执行销毁。

1、实例化/挂载阶段

  • constructor()

  • componentWillMount()

  • render()

  • componentDidMount()

export default class App3 extends Component {
    // 生命周期第一个阶段: 挂载/初始化阶段
    constructor(props){
        console.log("1.1 constructor: 构造初始化")
    }

    UNSAFE_componentWillMount(){
        console.log("1.2 componentWillMount")
        //做一些准备性的工作,比如提示正在加载
    }

    componentDidMount() {
        console.log("1.4 componentDidMount")
        //异步加载数据
    }
    render() {
        console.log("1.3 render")
        return (
            <div>
                <p>这是一个展示生命周期的组件</p>
            </div>
        )
    }
} 

2、存在期/更新期

  • componentWillReceiveProps()

  • shouldComponentUpdate()

  • componentWillUpdate()

  • render()

  • componentDidUpdate()

constructor(){
    this.state = {
        num:0
    }
}
render() {
    console.log("1.3/2.4 render")
    return (
        <div>
            <p onClick={this.handleClick.bind(this)}>这是一个展示生命周期的组件{this.state.num}</p>
        </div>
    )
}
handleClick(){
    this.setState({
        num:22
    })
}
// 生命周期第二个阶段  存在期/更新期
componentWillReceiveProps(){
    console.log("2.1 componentWillReceiveProps")
}
shouldComponentUpdate(nextProps, nextState) {
    console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")
    console.log("旧的值:", this.state.num)
    console.log("新的值:", nextState.num)
    // return true   则执行render
    // return false   则不执行render
    //这里返回值是布尔值,但不应该写死,
    //而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
    return this.state.num !== nextState.num
}
componentWillUpdate(nextProps, nextState) {
    console.log("2.3 componentWillUpdate 更新前的生命周期回调")
}
componentDidUpdate(prevProps, prevState) {
    console.log("2.5 componentDidUpdate 更新后的生命周期回调")
}

以上执行的是组件内部state数据更新前后的生命周期函数,

其实,对于组件的props属性值发生改变的时候,同样需要更新视图,执行render

componentWillReceiveProps() 这个方法是将要接收新的props值的时候执行,而props属性值从父组件而来,所以需要定义父组件:

class App3 extends Component {

    //生命周期第二个阶段  存在期/更新期
    UNSAFE_componentWillReceiveProps(nextProps){
        console.log("2.1 componentWillReceiveProps 这个方法props属性值更新的时候才会执行,更新state数据则不会执行这个方法")        
        console.log(nextProps)
    }
    shouldComponentUpdate(nextProps, nextState) {
        console.log("2.2 shouldComponentUpdate 可以判断修改前后的值是不是一样,不一样才去执行render。减少不必要的render,优化更新性能")
        //而是判断新旧两个值是不是不相等,不相等就要执行render(就要返回true)
        return (this.state.num !== nextState.num || this.props.fatherNum !== nextProps.fatherNum)  //不仅仅是state数据跟新时候需要执行render,props属性的值更新的时候也要执行render,所以要多加一个判断条件
    }
}
export default class Father extends Component{
    constructor(props){
        // 调用父类构造方法
        super(props)
        // 初始化状态数据
        this.state = {
            fatherNum:0
        }
    }
    componentDidMount() {
        setTimeout(() => {
            this.setState({
                fatherNum:10
            })
        }, 2000);
    }
    render(){
        return(
            <App3 fatherNum={this.state.fatherNum}/>
        )
    }
}

3、销毁期

  • componentWillUnmount() 销毁组件前做一些回收的操作
componentDidMount() {
    console.log("1.4 componentDidMount")
    document.addEventListener("click", this.closeMenu);
}
closeMenu(){
    console.log("click事件 closeMenu")
}
// 生命周期第三个阶段  卸载期/销毁期
componentWillUnmount() {
    console.log("3.1 componentWillUnmount 做一些回收的操作")
    document.removeEventListener("click", this.closeMenu);
}
  • index.js中3秒后重新渲染页面,就能触发组件的回收。
setTimeout(() => {
    ReactDOM.render(
        <div>hello world</div>
        , document.getElementById('root'));
}, 3000);

4、生命周期小结
React组件的生命周期 3大阶段10个方法 1、初始化期(执行1次) 2、存在期 (执行N次) 3、销毁期 (执行1次)

  • componentDidMount : 发送ajax异步请求

  • shouldComponentUpdate : 判断props或者state是否改变,目的:优化更新性能

Hooks(注意的是,hook只能使用在函数的最外层,任何别的位置都会报错。)

  • 1.useState实现函数式组件的响应式数据。 (StateHook)
    这是一个react提供好的用于实现state响应式特性的hook,使用如下:
export default function StateHook() {
  // 调用useState得到一个数组,顺便解构出来两个东西,一个是数据,一个是用于操作数据的函数
  const [count, setCount] = useState(0);
  const onClick = () => {
    // 调用对应的方法操作数据
    setCount(count + 1);
  };
  return (
    <div>
      {/* 直接渲染对应的数据 */}
      <p>Count:{count}</p>
      <button onClick={onClick}>增加</button>
    </div>
  );
}
  • 2.useContext实现多级组件间的数据传递。(ContextHook)
    ContextHook是用来解决多级组件之间数据传递的问题的。首先必须明确的是,函数式组件之间的父子数据传递数据,几乎和class组件是一样的。
//a.调用createContext创建一个Context对象
const MyContext = createContext();
//b.使用Provider组件提供一个“全局”数据
export default function ContextHook() {
  const [count, setCount] = useState(10);
  const add = (val) => {
    setCount(count + val);
  };
  return (
    <MyContext.Provider value={{ count, add }}>
      <Parent />
    </MyContext.Provider>
  );
}
//c.在后代组件里面使用useContext方法获取Context对象
function Parent() {
  return (
    <div>
      <Child />
    </div>
  );
}
function Child() {
  const context = useContext(MyContext);
  return (
    <div>
      {/* context对象就是之前的Provider里面的value属性 */}
      <p>count from ancestors:{context.count}</p>
      <button onClick={() => context.add(1)}>Add</button>
    </div>
  );
}
  • 3.useEffect模拟生命周期。(EffectHook)
    函数组件里面没有生命周期,我们可以使用EffectHook来模拟
    useEffect的基本用法如下:
//useEffect(当依赖发生变化时调用的函数, [依赖的数据]);
//使用EffectHook模拟生命周期:
//a.模拟componentDidMount
useEffect(() => {
    console.log("这是用来模拟挂载期的");
}, []);
//b.模拟componentDidUpdate
useEffect(() => {
    console.log("这是用来模拟更新期的");
}, [deps]);
//c.模拟componentWillUnmount
useEffect(() => {
    return ()=>{
        // 在这里面写卸载期的代码
        console.log("这是用来模拟卸载期的");
    }
}, []);

注意:
同个useEffect下,在检测销毁和检测字段更新之间,只能二选一。留下空数组,可以检测到return中的销毁。数组非空时,视图更新会带动return返回值,因此如果要检测字段更新,就无法检测销毁

  • 4.useRef获取DOM元素的引用。(RefHook)
    在函数组件里面我们可以使用userRef这个Hook获取一个元素或者组件的引用
import React, { useEffect, useRef } from "react";

export default function RefHook() {
  const btn = useRef();
  // 在componentDidMount后获取
  useEffect(() => {
    console.log(btn.current);
  }, []);
  return (
    <div>
      <button ref={btn}>Button</button>
    </div>
  );
}
  • 5.useReducer实现响应式数据操作 (ReducerHook)
    useReducer是useState的替代方案,当useState里面的逻辑相对复杂的时候,我们可以使用useReducer来代替。

useRducer的基本使用步骤如下

//a.准备一个初始state数据和操作state的方法
// 初始state
const state = {
  count: 0,
};
// 操作state的方法reducer
// aciont里面一般是type和pyload两个属性用来判断不同的复杂逻辑
const reducer = (st = state, action) => {
  const state = JSON.parse(JSON.stringify(st));
  // 通过判断不同的action来处理不同的逻辑
  switch (action.type) {
    case "add":
      state.count += action.pyload;
      break;
    case "reduce":
      state.count -= action.pyload;
      break;
  }
  // reducer一定要返回一个新的state
  return state;
};
//b.调用useReducer这个Hook来得到state和dispatch
export default function ReducerHook() {
  // state就是我们需要维护的数据,dispatch是一个方法,用来调用reducer的
  const [state, dispatch] = useReducer(reducer, initState);
  return (
    <div>
      <div>ReducerHook</div>
      <div>state's count : {state.count}</div>
      {/* dispatch方法传入一个action来激活reducer里面对应的操作 */}
      <button onClick={() => dispatch({ type: "add", pyload: 1 })}>Add</button>
      <button onClick={() => dispatch({ type: "reduce", pyload: 1 })}>
        reduce
      </button>
    </div>
  );
}

6.自定义hook

  • 使用hook可以让我们的数据的操作数据的逻辑放在一起,并且可以实现封装,把代码整理地更加易于维护。

  • 如果在操作数据的过程中有一些比较复杂的逻辑,我们就可以采用自定义hook的方式把这部分逻辑分离出来。

// 自定义hook要求以use开头

function useCount(initValue = 0) {
  const [count, setCount] = useState(initValue);
  const fn = () => {
    let val = 0;
    if (count > 0) {
      val = count - 1;
    } else {
      val = count + Math.floor(Math.random() * 10);
    }
    setCount(val);
  };
  return [count, fn];
}
export default function StateHook() {
  // 调用我们自定义的hook , 把复杂的逻辑全抽离到自定义hook,这样在我们的组件里面就尽可能简洁了
  const [count, setCount] = useCount();
  const onClick = () => {
    setCount();
  };
  return (
    <div>
      <p>Count:{count}</p>
      <button onClick={onClick}>增加</button>
    </div>
  );
}

路由
小节
1.使用react-router-dom进行组件的切换。
2.使用Outlet组件并配置子路由
3.使用useNavagate进行手动跳转。
4.使用路由进行数据传递(三种方式)。

1.下载安装
npm i --save react-router-dom@6 或 yarn add react-router-dom@6 --save
2.基本使用

// 1. 导入 reactrouter提供的组件
import { BrowserRouter, Routes, Route } from "react-router-dom";
export default function BasicRouter() {
  return (
    // 2.确定使用的是哪种路由模式(history/hash)
    <BrowserRouter>
      {/* 3.配置路由表 */}
      <Routes>
        {/* 4.配置每个路由path对应的组件 */}
        <Route path="/" element={<Index />}></Route>
        <Route path="/list" element={<List />}></Route>
        <Route path="/about" element={<About />}></Route>
      </Routes>
    </BrowserRouter>
  );
}

3.配置子路由

//a.直接在父路由里面配置子路由
<BrowserRouter>
    <Routes>
        <Route path="/" element={<Index />}>
            {/* 直接在父 Route下面配置子Route */}
            <Route path="list" element={<List />}></Route>
            <Route path="about" element={<About />}></Route>
        </Route>
    </Routes>
</BrowserRouter>
//b.在作为父路由对应的组件里面使用 Outlet 组件来展示子路由对应的组件
function Index() {
  return (
    <div>
      <div>Index</div>
      <Outlet />
    </div>
  );
}

4.使用Link作为跳转方式

//a.路由配置
<BrowserRouter>
    <Routes>
        <Route path="/" element={<Index />}>
            <Route path="list" element={<List />} />
            <Route path="about" element={<About />} />
        </Route>
    </Routes>
</BrowserRouter>
//b.在父组件里面配置Link和Outlet
import { Link, Outlet } from "react-router-dom";
function Index() {
  return (
    <div>
      <ul>
        <li><Link to="/list">List</Link></li>
        <li><Link to="/about">About</Link></li>
      </ul>
      <Outlet />
    </div>
  );
}

5.编程式导航
ReactRouter v6.x 使用useNavigate这个hook来进行跳转。

function Index() {
  // 1. 在组件里面使用 useNavigate 得到 navigate 函数
  const nav = useNavigate();
  return (
    <div>
      <div>Index</div>
      {/* 2. 在需要跳转的位置调用 navigate 函数跳转,直接传入一个path、数字或者路由配置对象 */}
      {/* 如果 navigate 函数接收的是一个数字, 负数表示后退,正数表示前进 */}
      <button onClick={() => nav("/state")}>
        about
      </button>
    </div>
  );
}

6.路由传参

//a.动态路由形式
//首先进行路由表配置
 <BrowserRouter>
    <Routes>
        <Route path="list/:id" element={<List />}></Route>
    </Routes>
</BrowserRouter>
//然后在对应的组件里面使用useParams这个hook进行数据获取
import { useParams } from "react-router-dom";
function List() {
  const params = useParams();
  console.log(params); // {id:xxx}
  return <div>List</div>;
}
//b.query参数
//如果有下面这样一个url
//http://www.domain.com/list?id=10&type=3
//如果想获取到 ? 之后的参数,我们需要使用 useSearchParams 这个hook进行操作
import { useSearchParams } from "react-router-dom";
function About(){
    // 调用 useSearchParams 得到query参数
    let [searchParams, setSearchParams] = useSearchParams();
    // 调用 searchParams 对象的 get 方法得到对应的数据
    console.log(searchParams.get('id')) // 10
    console.log(searchParams.get('type')) // 3
    return <div>About</div>
}
//c.state传参
//state传递数据的方式需要使用navigate手动跳转的时候传递。
//首先在A组件里面。
function Index() {
  const nav = useNavigate();
  return (
    <div>
      <div>Index</div>
      {/* navigate 函数的第二个参数可以传递state数据 */}
      <button onClick={() => nav("/state", { state: { id: 456 } })}>go to state page</button>
    </div>
  );
}
//然后在B组件里面使用location对象获取。
function State() {
  const location = useLocation();
  console.log(location.state); // { id:456 }
  return <div>State</div>;
}

ReactRedux
使用react-redux进行全局数据共享(Provider,userSelector,useDisaptch)。
ReactRedux是一套React专用的全局状态管理工具,在大型项目中也非常常用。
1.安装
react-redux是基于redux这个包的,所以我们需要在安装react-redux的时候,把redux一起安装
npm i redux react-redux 或者 yarn add redux react-redux
2.基本使用

//a.使用redux提供的createStore方法创建一个store对象
import { createStore } from 'redux'
const defState = {
  count: 100
}
const reducer = (state = defState, action) => {
  state = JSON.parse(JSON.stringify(state))
  switch (action.type) {
    case 'add':
      state.count += action.pyload;
      break;
  }
  return state
}
// 创建store对象,createStore方法需要一个reducer函数
const store = createStore(reducer)
export default store;
//b.使用react-redux提供的Provider组件全局提供数据
import store from "./store.js";
import { Provider } from "react-redux";

export default function BasicStore() {
  return (
    // Provider 需要一个store属性,这个store属性就是我们的store对象
    <Provider store={store}>
      <div>BasicStore</div>
      <Child />
    </Provider>
  );
}
//c.在Provider包着的后代组件里面使用 userSelector 和 useDisaptch 对store里面的数据进行操作
import { useDispatch, useSelector } from "react-redux";
function Child() {
  // useSelector方法可以根据一个函数得到state里面的数据
  const count = useSelector((state) => state.count);
  // dispatch 是一个函数,用来触发 在 recuder 里面的 action 来操作数据
  const dispatch = useDispatch();
  return (
    <div>
      <div>Child</div>
      <div>{count}</div>
      {/* 直接调用 dispatch 触发 reducer 里面对应的 action */}
      <button onClick={() => dispatch({ type: "add", pyload: 2 })}>Add</button>
    </div>
  );
}

3.connect组件使用redux
其实这种方式还算比较复杂,已经不推荐使用,但是如果是在class组件里面,因为我们没法使用hook,所以需要了解一下这些用法。

function Ch(props) {
  return (
    <div>
      <div>Child</div>
      {/* 直接使用props访问store里面的数据 */}
      <div>{props.count}</div>
      {/* 直接使用props访问store里面的方法 */}
      <button onClick={props.add}>Add</button>
    </div>
  );
}
// 这个函数用于将store的数据映射到组件的props上
const mapStateToProps = (state) => ({ count: state.count });
// 这个函数用于将store的dispatch映射到组件的props上
const mapDispatchToProps = (dispatch) => ({
  add() {
    dispatch({ type: "add", pyload: 2 });
  },
});
// 使用react-redux提供的 connect 方法将组件进行加工,将数据映射到props上
const Child = connect(mapStateToProps, mapDispatchToProps)(Ch);
posted @   Gawaine  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示