dom树,React 负责操控JSX 元素也可以像 HTML 元素一样拥有属性,一个 JSX 属性使用类似 HTMl 语法编写,一个名称后面跟着一个等于号,接着是一个值,这个值应该用一对引号将其包裹,像这样:
my-attribute-name = "my-attribute-value"下面是一些带属性的 JSX 元素
<a href="http://www.example.com">Welcome to the Web</a>;
const title = <h1 id="title">Introduction to React.js: Part I</h1>;一个单独的 JSX 元素可以像 HTML 一样有很多元素
const panda = <img src = 'images/panda.jpg' alt = 'panda' width = '500px' height = '500px'>JSX 外部元素有个规则:一个 JSX 表达式只能有一个最外层元素.
以下代码可以解析:
const paragraphs = (
<div id="i-am-the-outermost-element">
<p>I am a paragraph.</p>
<p>I, too, am a paragraph.</p>
</div>
);但是这个代码就无法解析了
const paragraphs = (
<p>I am a paragraph.</p>
<p>I, too, am a paragraph.</p>
);所以 JSX 表达式最外层最好用<div></div> 标签包裹
ReactDOM是一个JavaScript 库的名字,这个库中包含了几个React特有的方法,这些方法大部分都是用来处理DOM元素的.
ReactDOM.render() 是渲染 JSX 最常用的方法,它接受一个 JSX 表达式,创建一个对应的 DOM 节点树,并且将该树添加到 DOM 中.
参数一: JSX 表达式,它将被渲染到屏幕上
ReactDOM.render(<h1>Hello world</h1>, document.getElementById('app')),使用原生的 map 来进行,重复渲染的是哪一个模板就 return 谁
注: 遍历列表的时候同样需要一个类型为 number/string 的不可重复的 key 来提高 diff 性能
key 不会被渲染到真实 dom 中,仅仅存在于 React 中
const names = ["赵", "钱", "孙", "李", "周", "吴", "郑", "王"];
export default function testDemo() {
return (
<div>
<p>列表渲染</p>
<ul>
{names.map((i) => (
<li key={i}>老{i}头</li>
))}
</ul>
</div>
);
}export default function CanSmoke(props) {
const age = props.age;
return (
<div>
<h1>Hello!</h1>
{age > 18 && <h3>you can somoke!</h3>} // 与运算符,年龄大于18岁可以抽烟
</div>
);
}
// main.jsx
<Condition age={28} />;
props 是组件对外的接口,state 是组件对内的接口。

props
props 也叫做属性props 只能在该组件的上层组件中修改state
state 也叫做状态单向数据流
避免了数据在组件中来回传递
graph TD
A[<父组件>] --> |数据| B[<子组件>] -->|更新界面| A组件挂载后立即执行的操作,有很多依赖于 DOM 节点初始化的操作放置在这里。如:通过网络请求资源,或者加载事件监听器。
在组件卸载或者卸载之前直接调用。一般用来执行必要的清理操作,例如: 清除 timer,取消网络请求,移除相同的事件监听器。
什么是 hook ?
hook 就是 JavaScript 函数,能够让函数组件中使用 React 特性,通常名字都是以 use 开头,但是运行得时候具有额外得规则:
import React, { useState } from "react";
function Example() {
const [count, setCount] = useState(0);
// 声明一个叫做 "count"得 state 变量
}useState 返回两个变量,一个是当前 state ,另一个是更新 state 的函数
对环境的改变即为副作用,比如修改 document.title ,如果只是改变自己的状态就不是副作用,改变环境或者全局变量就是副作用
用来模拟 class 组件中的 componentDidMount componentWillUnmount 和 componentWillUnmount 的功能。
useEffect 的默认行为是在第一次渲染之后和每次更新之后都会执行,相当于挂载和更新。或者说是在 DOM 渲染之后进行(afterRender)。
/*
1.作为componentDidMount使用,[]作第2个参数
2.作为componentDidUpdate使用,可指定依赖
3.作为componentWillUnmount使用,通过return
*/
const { useState, useEffect } = React;
function App() {
const [num, setNum] = useState(0);
const onclick = () => setNum(num + 1);
// 第一次渲染的时候执行
useEffect(() => {
console.log(`我是第一次渲染的时候执行的`);
}, []);
// 每次update都会执行(默认行为)
useEffect(() => {
console.log(`每次更新的时候执行的函数,update`);
});
// 条件更新(用于监听某个值变化时执行,包含第一次)
useEffect(() => {
console.log(`只有num更新了才会执行`);
}, [num]);
// 在条件中判断排除第几次更新不监听
useEffect(() => {
if (num !== 2) {
console.log(`只有num不为2的时候才执行`);
}
}, [num]);
return (
<>
<button onClick={onclick}>点击加一</button>
<h1>{num}</h1>
</>
);
}useEffect( ) 传两个参数,第一个是函数,第二个是数组 []
[] 时,表示只会在第一次渲染后执行前面的函数。[n] 的时候,表示只有 n 的值发生变化的时候才会执行前面的函数,包括第一次useReducer 是一个状态管理的 hook api 是 useState 的替代方案。
state 的类型是 number ,string boolean 建议使用 useState ,如果 state 的类型为 object 或者 Array 使用 useReducerstate 的变化非常多, 建议使用 useReducer 几种管理 state 状态useState 如果像维护全局 state 使用 useReducerReact 中组件树之间相互通信只能数据向下流动,事件向上流动。但是如果组件层级过大,一层层之间相互流动都需要手动添加 props 。就像一层层的打洞,而使用 Context 就可以在组件树中进行数据传递。

对于一个组件树来说,context 就是它的全局数据变量,只要层级比它低的都能接受访问到这个数据。一般来说在以下方面会使用到,
函数式组件为例
createContextconst MyContext = createContext(defaultValue); // 这里传入数据对象只有当订阅了 Context 对象的组件没有在组件树中匹配到 provider 的时候,defaultValue 才会生效
context 就像一层壳子包裹需要使用数据的组件,从这个 '壳子' 中可以访问数据
Provider 指定使用的范围在圈定的范围内,传入读操作和写操作对象,可以使用上下文
<MyContext.provider value={{ n, setN }}>
<p>Num {n}</p>
<Child />
</MyContext.provider>useContext使用 useContext 来接受上下文,因为传入的是对象,则接受的也是因该是对象。
const { n, setN } = useContext(c);/* 先构造一个context,可以添加默认值,当没有数据提供者的时候从默认值中获取 */
const CompanyContext = createContext();
class CompanyContainer extends Component {
state = {
companyName: "光华钢铁贸易公司",
employees: 1234566767,
name: "赵大强",
teamName: "改革机动小组",
teams: 200,
title: "总经理",
};
render() {
return (
<CompanyContext.Provider value={this.state}>
{" "}
// 将state中的数据传递给Context提供者
<Company /> // 第一个子组件
</CompanyContext.Provider>
);
}
}
const Company = () => (
<CompanyContext.Consumer>
{" "}
// Context内容的消费者
{({ companyName, employees, teams }) => (
<>
<h1>
公司名: <span>{companyName}</span>
</h1>
<p>
成员: <span>{teams}</span>
</p>
<Team /> // Team 子组件
<p>
联系电话: <span>{employees}</span>
</p>
<Employee /> // Employee子组件
</>
)}
</CompanyContext.Consumer>
);
const Team = () => (
<CompanyContext.Consumer>
{({ teamName }) => (
<p>
我来自: <span>{teamName}</span> 团队。
</p>
)}
</CompanyContext.Consumer>
);
const Employee = () => (
<CompanyContext.Consumer>
{({ name, title }) => (
<p>
我是一个总经理,我的名字是:{name},我的头衔是 <span>{title}</span>
</p>
)}
</CompanyContext.Consumer>
);
usrRef神奇的地方出了可以在不 re-render 的状态下更新值,也可以直接操作 DOM 从而控制 DOM 行为
和 useState 不同的是,useState 返回一个数组,第一个是 state 第二个是修改state 的方法。每次重新设置state值的时候,就会触发重新渲染。
const [count, setCount] = useState(0);
// count:0
const [count] = useRef(0);
// count: {current: 0}而 useRef 只返回一个 object 这个个对象包含 current 属性,每次更新 current 的值的时候不会触发重新渲染。
render 的次数const App1 = () => {
const renderCount = useRef(0); // { current: 0 }
const [count, setCount] = useState(0);
useEffect(() => {
renderCount.current += 1;
});
console.log(renderCount);
return (
<div>
<p>renderCount.current</p>
<button onClick={() => setCount(count + 1)}>add</button>
<h1>{count}</h1>
</div>
);
};dom// 用处二: 在react中操作dom
function App2() {
const textEl = useRef(); // dom节点的值被传入了 ref对象的 current中
const clickHandler = () => (textEl.current.innerText = "hello world");
return (
<div>
<button onClick={clickHandler}>点我改变h1标签内容</button>
<h1 ref={textEl}>Nice</h1>
</div>
);
}previouts state 的值,在函数式组件中,用于无法得知上一个状态的 state 是什么,因为每一次的 render 都是一个全新的状态, 这个时候就需要使用 useRef 了,因为 useRef 中的 current 值得改变并不会导致 re-render// 用处三: 获得 Previous 的值
function App3() {
const [name, setName] = useState("");
const previousName = useRef("");
const countRender = useRef(0);
useEffect(() => {
previousName.current = name;
}, [name]);
useEffect(() => {
countRender.current += 1;
});
return (
<>
<input type="text" onChange={() => setName(event.target.value)} />
<hr />
<p> My name is {name} </p>
<p> My previous name is {previousName.current} </p>
<p>Render times is {countRender.current} </p>
</>
);
}useRef 多用在不涉及画面显示的时候,才会使用来控制 dom
{} 包裹,使用箭头函数的时候注意返回的是一个整体,用括号包裹而不是花括号provider 可以嵌套使用,内层的 value 覆盖外层的 valueprovider 内的 value 发生变化的时候,内层的消费组件都会重新渲染有的组件中可能有着大量重复的计算,组件数据变更后默认是重新计算数据的,如果这个数据每次都是一样的输出,可以将数据存储起来,不用每次都重新 render。
const memoizedValue = useMemo(fn(a, b), [a, b]); // 数组中放置的是每次函数计算所需要的依赖项返回一个记忆化的 callback ,就是在依赖没有改变的情况下,把某个 function 保存下来。
const memoziedCallback = useCallback(() => {
doSomething(a, b), [a, b];
});useCallback 和 useMemo 异同

useCallback 返回 callback function ,所以可以传参数进去useMemo 返回值useCallBack(fn,deps) 等同于 useMemo(() => fn,deps)自定义 hook 最大的作用就是抽象利用逻辑,可以多次复用。自定义的 hook 就是一个函数,名称默认为 use 开头,函数内部可以调用其他的 hook
返回按键
function useKeyPress() {
const [pressKey, setKey] = useState(undefined);
const handleKeyPress = ({ code }) => setKey(code);
useEffect(() => {
window.addEventListener("keypress", handleKeyPress);
return () => window.removeEventListener("keypress", handleKeyPress);
}, []);
return pressKey;
}返回鼠标按键
function useMousePos() {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
function onMove(event) {
setPos({ x: event.x, y: event.y });
}
window.addEventListener("mousemove", onMove);
return () => {
window.removeEventListener("mousemove", onMove);
};
}, []);
return pos;
}计时器
function useInterval() {
const [times, setTimes] = useState(0);
useEffect(() => {
const id = setInterval(() => setTimes((times) => times + 1), 1000); // 由于这个函数只在组件挂载成功时运行一次,此时的time就是组件刚创建时候的值,就是0
return () => {
clearInterval(id);
};
}, []);
return times;
}追踪浏览器窗口尺寸
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
const handleResize = () =>
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return windowSize;
}强制组件更新
function useForceUpdate() {
const [i, setI] = useState(0);
function forceUpdate() {
setI((i) => i + 1);
}
return useCallback(forceUpdate, []);
}function useToggle(initialState) {
const [value, setValue] = useState(initialState);
const toggleValue = useCallback(() => setValue((prev) => !prev), []);
return [value, toggleValue];
}function useHover() {
const [isHovering, setIsHover] = useState(false);
const hoverRef = useRef();
useEffect(() => {
const enter = () => setIsHover(true);
const leave = () => setIsHover(false);
hoverRef.current.addEventListener("mouseenter", enter);
hoverRef.current.addEventListener("mouseleave", leave);
return () => {
hoverRef.current.removeEventListener("mouseenter", enter);
hoverRef.current.removeEventListener("mouseleave", leave);
};
}, [hoverRef.current]);
return [hoverRef, isHovering];
}有时候一个组件需要返回多个并列元素
function App() {
return (
<p>I would</p>
<p>really like</p>
<p>to render</p>
<p>an array</p>
);
}
// 错误语法,多个并列元素需要用div包裹当渲染返回多个同级标签的时候,JSX 转换器发现最外层元素是多个元素,而不是单个元素的时候,会不知道渲染哪一个 tag,所以会报错
class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns /> // 此处的Columns是横向子组件,外层为 tr 标签
</tr>
</table>
);
}
}这个 Columns 组件外层元素是 tr 标签,也意味着这组件需要返回多个 <td> 但是如果这个子组件返回多个 td 语法上就是错的,需要包裹一层 div 作为父元素,这样的话,整个组件最终渲染出来的html 是无效的。
function () {
return (
<div>
<td>hello</td>
<td>one</td>
<td>two</td>
</div> // 作为组件返回时会失效不显示,打破了层级关系
)
}<div> 包裹,但是会在 DOM 上添加额外的节点[ ] 包裹成一个数组返回,注意数组元素之间需要添加 ,最新的方法,使用Fragment ,可以为子元素分组而不添加额外的节点
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}还有一种更简单的短语法
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}function Glossary(props) {
return (
<dl>
{props.items.map(item => (
//如果没有key,React将会展示一个key warning
< key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</>
))}
</dl>
);
}React 的本质是前端的 UI 库,和 Vue 这种框架是有区别的, Vue 的官网说明了自己是一个渐进式的框架。
React 写的是组件,一个页面可以有多个组件构成,组件相互组合构成了一个完整的页面。颗粒度更细,我们只需要关注每一个组件怎么实现的state 状态React 是组件化开发,我们只需要关注组件如何实现。组件写好了就是静态的, 组件想要发生改变就需要数据进行驱动。于是产生了两种数据,state (内部数据),props (外部数据),数据的改变触发组件状态的改变。
用户的交互操作==> 数据状态的改变 ==> View视图的改变好处:2009年Node.js的创立使Javascript不再局限于前端
Jquery 这种库能够流行的原因,数据驱动的话只需要判断数据的变化,进行修改,简化操作。React 带来组件化开发和数据驱动,React 自身就是一个 基础的ui库,前端开发的其他功能都需要社区提供的配套 React-Router ,Redux, 正是先有了 组件化和数据驱动 的思想,为了达到这种设计模式,最终才实现了 React .
React 最初的发展受到面向对象的思想影响束缚,直到 16.8版本中 hooks 机制横空出示,React 才彻底和历史决裂拥抱函数式编程思想。