React 入门学习

112 阅读11分钟

# 对于React的学习

简介

React是Facebook公司的一个内部项目,最初用于自己构造Facebook网站,后来于2013年5月开源。由于拥有较高的性能,且代码逻辑非常简单,越来越多的人已开始关注和使用它。

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、单向数据流

对于jsx的理解

react是单向数据流,父组件传递给子组件的数据,子组件能够使用,但是不能直接通过this.props修改。 这样让数据清晰代码容易维护。

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

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

1.JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。

2.它是类型安全的,在编译过程中就能发现错误。

3使用 JSX 编写模板更加简单快速。

1.在js里面写html

JSX语法允许我们直接在js里面写HTML代码

ReactDOM.render(
  <div>HTML</div>,
  document.getElementById('root')
)

但是在jsx里面推荐使用一个小括号来把HTML结构包起来

ReactDOM.render(
  (
    <div>HTML</div>
  ),
  document.getElementById('root')
)

2.在html里面写js

JSX除了允许在js里面写HTML外,还可以在HTML里面写js,只是要求一定要在html里面加上一对大括号,然后在大括号里面写js。

jsx的大括号里面可以写你想要的表达式,可以可以写对应要渲染的变量之类的。最终会被作为js执行并渲染到页面上。

const person = { name:'狗蛋' };
ReactDOM.render((
  <div>{ person.name }今年{ Math.floor(Math.random() * 100) }岁了</div>
),
  document.getElementById('root')
)

大括号,相当于给html内部开辟了一个执行js的空间。

3.关于文件的后缀

react里面默认支持的可以写jsx语法的后缀有: .js/.jsx 使用ts的话文件名为.ts/.tsx

总结

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

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

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

4.js/jsx/tsx文件里面都可以写jsx语法

组件化开发

class组件

在早期的react用法里面,主要是class组件,但是因为这种方式量级比较重,以及使用起来比较繁琐,后期维护难度比较高,所以在后期的项目中已经基本不使用

定义一个class组件的基本格式如下。

import { Component } from "react";

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

函数式组件

react16.8开始,因为hook的推出,极大地提升了函数式组件的实用性,所以基本在外面的项目中,都全面转型到了 FC + Hook 的用法。

定义一个函数组件的基本格式如下:

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

函数式组件只有一个函数,需要返回一个jsx结构。相比于class组件而言,非常地轻量。

react中的样式操作

1.样式操作

在react里面同样支持行内样式和外链样式。

a.外链样式

在组件里面引入对应的外部样式就行。

import React, { Component } from "react";
import "./tab.css";

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

b.行内样式

react里面只支持使用对象的方式声明行内样式。

export default class Tab extends Component {
  render() {
    return (
      <div
        style={{
          width: "400px",
          minHeight: "400px",
          border: "1px solid #ccc",
          margin: "0 auto",
        }}
      >
        Tab
      </div>
    );
  }
}

我们可以使用类名的方式控制样式。因为class属性与es6的class关键字会产生冲突,所以在react里面使用了className代替了class属性。

c.className

当然我们也可以使用类名的方式控制样式。但是要注意,因为class属性与es6的class关键字会产生冲突,所以在react里面使用了className代替了class属性。

export default class Tab extends Component {
  render() {
    return (
      <div>
        <div className="tab-out tab-b">Tab</div>
        <div className={"tab-out"}>Tab</div> 
        <div className={["tab-out", "tab-b"].join(" ")}>Tab</div>
      </div>
    );
  }
}

在react里面还有一个html属性也是和js的关键字冲突的。label标签有一个for属性,也是和js的for是冲突的,所以在react里面使用labelFor来代替。

列表渲染

在react里面没有类似vue的指令,所以我们必须自己手动循环生成。在react里面我们一般采用map方法对数组进行循环,因为map方法可以返回我们想要渲染的jsx结构。

const tabItems = ["首页", "新闻", "购物", "其它"];
const tabContent = [
  "欢迎您,狗蛋先生",
  "今日,全球首富狗蛋先生和翠花女士结为夫妇",
  "没钱,买什么买",
  "...",
];

export default class Tab extends Component {
  render() {
    return (
      <div className="tab-out">
        <ul className="flex">
          {tabItems.map((item, key) => (
            <li key={key}>{item}</li>
          ))}
        </ul>
        <div className="content">
          {tabContent.map((item, key) => (
            <section key={key}>{item}</section>
          ))}
        </div>
      </div>
    );
  }
}

在react里面,为了保证diff算法对于dom对象的重用处理,也需要给每个循环生成的结构添加一个key属性。

父组件向子组件传递数据

react里面父组件向子组件传递数据比较简单,只需要给子组件一个自定义属性传递就行。

<div className="todo">
    {/* 1. 使用一个自定义属性向子组件传递数据 */}
    <TodoList title="正在进行"/>
</div>

然后我们在子组件里面使用this.props进行接收

export default class TodoList extends Component {
  render() {
    return (
      <div className="list">
        <h2>{this.props.title}</h2>
      </div>
    );
  }
}

子组件向父组件传递数

react里面子组件不能直接修改父组件里面的数据,只能通过调用父组件的方法来实现,也就是子组件向父组件传递数据的过程。

export default class Todo extends Component {
  // 1. 在父组件里面准备一个用于操作数据的方法
  addSome(value) {
    console.log(value);
  }
  render() {
    return (
      <div className="todo">
        {/* 2. 在子组件上直接传递操作数据的方法 */}
        <TodoHeader add={this.addSome.bind(this)} />
      </div>
    );
  }
}
export default class TodoHeader extends Component {
  onButtonClick() {
    // 3. 在子组件里面通过this.props获取并调用父组操作改数据的方法
    this.props.add(this.state.value);
  }
  render() {
    return (
      <div className="flex todo-header">
        <button onClick={this.onButtonClick.bind(this)}>添加</button>
      </div>
    );
  }
}

多级组件传递数据

在react里面,多级组件之间传递使用得不多,但是我们还是需要掌握。

在react里面我们使用context的方式进行多级传递。其具体使用过程如下:

1.首先调用createContext方法创建一个“全局”context对象。

import { createContext } from "react"
// 声明一个defaultValue用来声明context的数据结构
export const defaultValue = { count:0 , add(val){} }
// 创建context对象
export const TodoContext = createContext(defaultValue)

2.在“最高级”的外层组件使用 Provider 组件提供context对象

import React, { Component } from "react";
import { MyContext } from "./context";
import Child from "./Child";

export default class Parent extends Component {
  constructor(props) {
    super(props);
    // 声明一个state,用于共享数据,结构和context的defaultValue保持一致
    this.state = {
      count,
      // 这是一个用于修改state数据的方法
      add: this.addFn.bind(this),
    };
  }
  addFn(val) {
    const count = (this.state.count += val);
    this.setState({ count });
  }
  render() {
    return (
        // 使用value属性来给Provider包着的组件提供数据,所有被它包着的组件都可以得到这这些数据
        <Provider value={this.state}>
            <Child />
        </Provider>
    );
  }
}

3.在需要使用“全局”数据的地方使用Consumer组件获取并操作数据

import React, { Component } from "react";
import { MyContext } from "./context";

export default class Child extends Component {
  render() {
    return (
      // Consumer组件可能帮我们得到对应的context里面的数据,Consumer组件里面必须使用一个函数返回jsx结构
      <MyContext.Consumer>
        {/* 这个函数的value参数就是Provider的value属性*/}
        {(value) => (
          <div>
            {/* 获取数据 */}
            <div>{value.count}</div>
            <button
              // 操作数据
              onClick={() => {
                value.add(1);
              }}
            >
              ++
            </button>
          </div>
        )}
      </MyContext.Consumer>
    );
  }
}

生命周期

React生命周期可以分成三个阶段:

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

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

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

1、实例化/挂载阶段

constructor()

componentWillMount()

render()

componentDidMount()

2、存在期/更新期

存在期:且件已经渲染好并且用户可以与它进行交互。通常是通过一次鼠标点击、手指点按者键盘事件来触发一个事件处理器。随着用户改变了组件或者整个应用的state,便会有新的state流入组件树,并且我们将会获得操控它的机会。

componentWillReceiveProps()

shouldComponentUpdate()

componentWillUpdate()

render()

componentDidUpdate()

3、销毁期

componentWillUnmount() 销毁组件前做一些回收的操作

4、生命周期总结

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

componentDidMount : 发送ajax异步请求

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

hook

EffectHook

import React, { useEffect, useState } from "react";

export default function EffectHood() {
  const [count, setCount] = useState(0);
  // 依赖于数据的变化,数据一旦发生了变化就会被调用
  useEffect(() => {
    console.log("数据发生了变化");
  }, [count]);

  // 模拟数组的生命周期 模拟 didMount-组件挂载完成
  useEffect(() => {
    let h2 = document.querySelector("h2");
    console.log(h2);
  }, []);

  // 模拟didUpdate生命周期  页面更新
  useEffect(() => [console.log("模拟了didUpdate")], [count]);

  // 模拟willUnmount
  useEffect(() => {
    return () => {
      console.log("组件要被销毁了");
    };
  }, []);

  return (
    <div>
      <h2>EffectHood</h2>
      <p>count:{count}</p>
      <p>
        <button onClick={() => setCount(count + 1)}>按钮</button>
      </p>
    </div>
  );
}

Refhook

/**
 *  在react里面
 *      获取class组件的实例,或者 dom 元素 ,就可以使用 RefHook
 *
 */

import React, { Component, useRef } from "react";

export default function RefHook() {
  const ref = useRef();
  const ref2 = useRef();
  //   console.log(ref.current);
  // 函数组件是没有生命周期的 - 等等会使用 useEffect 模拟生命周期
  setTimeout(() => {
    console.log(ref.current);
    console.log(ref2.current);
  }, 2000);

  return (
    <div>
      <h4 ref={ref}>RefHook</h4>
      {/* 函数组件是没有实例的,所以无法使用ref获取 */}
      {/* <Child ref={ref2} /> */}
      <Child2 ref={ref2} />
    </div>
  );
}

function Child() {
  return <div>Child Function</div>;
}

class Child2 extends Component {
  render() {
    return <div>Child Class</div>;
  }
}

StateHook

/**
 *  hook
 *      一堆 react 提供的 函数 , 就是要学习这些函数 有什么用,怎么用
 *
 *  useSate
 *      作用: 实现响应式数据
 *      用法:
 *            const [响应式数据,修改数据的方法] = useState(初始值)
 *
 */

import React, { useState } from "react";

export default function StateHook() {
  // 注意点: 1. hook只能在函数组件里面使用
  const [count, setCount] = useState(10);
  //   2. 只能在函数的最外层使用
  //   if (true) {
  //     const [count, setCount] = useState(10);
  //   }
  return (
    <div>
      <h3>StateHook</h3>
      <p>Count: {count}</p>
      <p>
        <button onClick={() => setCount(count + 1)}>按钮</button>
      </p>
    </div>
  );
}

CusHook

/**
 *  自定义hook
 *
 */

import React, { useState } from "react";

// 自定义hook也是要以use开头的
function useCount() {
  const [count, setCount] = useState(10);
  const clickHandle = () => {
    setCount(count + 1);
  };
  const add = () => {
    const temp = count + 1 / 2 + 15 * 3.14;
    setCount(temp);
  };
  return [count, clickHandle, add];
}

export default function CusHook() {
  const [count, clickHandle, add] = useCount();

  const [age, setAge] = useState(10);
  const handle2 = () => {
    setAge(age + 1);
  };
  // 将来在 函数组件里面的 useState 可能会越写越多,导致 函数组件特别的庞大
  // 我们在使用hook的时候,还把一些复杂的逻辑,抽离出来 ,形成 自定义hook
  return (
    <div>
      <h3>StateHook</h3>
      <p>Count: {count}</p>
      <p>
        <button onClick={clickHandle}>按钮</button>
      </p>
    </div>
  );
}

ReducerHook

import React, { useReducer, useState } from "react";

// export default function ReducerHook() {
//   const [count, setCount] = useState(0);
//   //   可能我们在一个页面上,对于这个数据的操作比较复杂
//   const add = () => {
//     setCount(count + 1);
//   };
//   const subtruct = () => {
//     setCount(count - 1);
//   };
//   const cheng = () => {
//     setCount(count * 2);
//   };
//   const chu = () => {
//     setCount(count / 2);
//   };
//   return <div>ReducerHook</div>;
// }

// 使用自定义hook放到外面
// function useCount() {
//   const [count, setCount] = useState(0);
//   //   可能我们在一个页面上,对于这个数据的操作比较复杂
//   const add = () => {
//     setCount(count + 1);
//   };
//   const subtruct = () => {
//     setCount(count - 1);
//   };
//   const cheng = () => {
//     setCount(count * 2);
//   };
//   const chu = () => {
//     setCount(count / 2);
//   };
//   return [count, add, subtruct, cheng, chu];
// }

// export default function ReducerHook() {
//   const [count, add, subtruct, cheng, chu] = useCount();
//   return <div>ReducerHook</div>;
// }

// 继续优化
// 我们只对外暴露两个东西,一个 响应式数据,一个是修改的方法
// function useCount() {
//   const [count, setCount] = useState(0);
//   //   可能我们在一个页面上,对于这个数据的操作比较复杂
//   const add = () => {
//     setCount(count + 1);
//   };
//   const subtruct = () => {
//     setCount(count - 1);
//   };
//   const cheng = () => {
//     setCount(count * 2);
//   };
//   const chu = () => {
//     setCount(count / 2);
//   };

//   const fn = (type) => {
//     // 在这个处理的函数里面,判断,你想如何操作数据
//     if (type == "add") {
//       add();
//     } else if (type == "sub") {
//       subtruct();
//     }
//     // ...
//   };
//   return [count, fn];
// }

// export default function ReducerHook() {
//   const [count, fn] = useCount();
//   return (
//     <div>
//       <h4>ReducerHook</h4>
//       <p>count 是 : {count}</p>
//       <p>
//         <button onClick={() => fn("add")}>add</button>
//         <button onClick={() => fn("sub")}>sub</button>
//       </p>
//     </div>
//   );
// }

// 基于上述的逻辑,react里面帮我们封装好了一个 reducerHook
// 1. 先准备一个reducer函数

// state 是响应式数据 , action 是一个操作
const reducer = (state, action) => {
  // 在reducer函数里面判断 action 是什么操作,然后进行对应的数据处理
  switch (action.type) {
    case "add":
      state.count++;
      break;
    default:
      break;
  }
  // reducer 函数一定要返回一个state
  //   每次返回的state,一定要保证是一个新的引用
  return { ...state };
};

export default function ReducerHook() {
  // 2.在函数组件里面使用 useReducer 实现上面的代码
  // dispatch是修改响应式数据的委托
  const [data, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <div>
      <h4>ReducerHook</h4>
      <p>count : {data.count}</p>
      <p>
        {/* dispatch({type}) */}
        <button onClick={() => dispatch({ type: "add" })}>++</button>
      </p>
    </div>
  );
}