一. 前置知识
- vue的模板语法有vue-loader
- react 的 jsx 语法使用的jsx-loader被babel-loader内置了,而babel-loader被webpack内置了
- JSX 注意事项
- class属性 是className
<div className ="red">n</div>
会被转译为React.createElement('div',{className:'red'},'n')
-
变量和对象用花括号{}包起来
-
return 后面加括号()
- React 引入
yarn add react react-dom
import React from 'react'
import ReactDom from 'react-dom'
二. Class组件
1. 创建方式
class Welcome extends React.Component{
constructor(props){
super(props)
this.state={
n:0,
m:1,
user:{
name:"xiaohong",
age: 18
}
}
}
render(){
return (<h1>hello,{this.props.name}</h1>)
}
}
//使用方法
<Welcome name = "xiaoming">
2. 读取props
- 不准写props,通知父组件修改
//传props,props会被包装成一个对象{name:"xiaohong",onClick:()=>{},children:"hi"}
class Parent extends React.component{
constructor(props){
super(props)
this.state ={name:"xiaohong"}
}
onClick =()=>{}
render(){
return <B name = {this.state.name} onClick={this.onClick}>hi</B>
}
}
//读
class B extends React.component{
constructor(props){
super(props) //这样写了以后,this.props就是外部数据 对象的地址 了
}
render(){
return <div onClick = {this.props.onClick}>
{this.props.name}
<div>
{this.props.children}
</div>
</div>
}
}
3. 读写state
注意事项:
- setState 会自动合并第一层属性,当有多层时使用Object.assign()或...操作符
- this.state.n+=1 无效,必须调用 setState()才会触发UI更新
- setState会异步更新UI,立马读state会失败,更推荐的方式是 setState(函数)
- this.setState(this.state)不推荐,不要修改旧state(不可变数据,函数式编程理念)
- this.setState((state,props)=>newState,fn) fn在写入成功后执行
//读
this.state.n
//写
this.setState({n:1})
//多层属性时-方法1...操作符 ,m 和 n 会自动合并
this.setState({
user:{
...this.state.user
name:jack
}
})
//多层属性时-方法2-Object.assign,m 和 n 会自动合并
const user =Object.assign({},this.state.user)
user.name ="xiaofang"
this.setState({
user:user
})
//函数写法
this.setState((state)=>
( {n:state.n+1})
)
4. 事件绑定
//addN在对象上
class Son extends React.Component{
constructor(props){
super(props)
this.state={
n:0
}
this.addN=()=>{
this.setState({n:this.state.n+1})
}
}
render(){
return <button onclick ={this.addN}>+1</button>
}
}
//语法糖,把addN提到constructor外面,和上面的写法完全等价
class Son extends React.Component{
constructor(props){
super(props)
this.state={
n:0
}
}
addN=()=>{
this.setState({n:this.state.n+1})
}
render(){
return <button onclick ={this.addN}>+1</button>
}
}
//addN在原型上,如果不bind this ,this 会变成window,window上没有addN函数
class Son extends React.Component{
constructor(props){
super(props)
this.state={
n:0
}
}
function addN(){
this.setState({n:this.state.n+1})
}
render(){
return <button onclick ={()=>this.addN()}>+1</button>
}
}
//另一种
class Son extends React.Component{
constructor(props){
super(props)
this.state={
n:0
}
}
function addN(){
this.setState({n:this.state.n+1})
}
render(){
return <button onclick ={this.addN.bind(this)}>+1</button>
}
}
5. 生命周期钩子函数
1. constructor() //初始化state
2. static getDerivedStateFromProps()
3. shouldComponentUpdate() return false时阻止更新
4. render() 创建虚拟dom
5. getSnapshotBeforeUpdate()
6. componnetDidMount() //组件出现在页面
获取DOM
Ajax请求
首次渲染执行此钩子
7. componentDidUpdate() //组件已更新
也可以发起Ajax请求
首次执行不会执行此钩子
此处setState可能会触发无限循环,除非放在if里
shouldCoponentUpdate返回false时不执行此钩子
8. componnetWillUnmount() //组件将死
9. static getDerivedStateFromError()
10. componnetDidCatch()
//shouldComponentUpdat() return false时阻止更新
//React.PureComponent内置了此功能,当props和state未发生改变时就不会重新render
class App extends React.component{
constructor(props){
super(props)
this.state = {
n:1
}
}
onClick = ()=>{
this.setState(state=>({n: state.n + 1}))
this.setState(state=>({n: state.n - 1}))
}
shouldComponentUpdate(newProps,newState){
if(newState.n === this.state.n){
return false
}else{
return true
}
}
render(){
console.log("render了一次")
return(
<div>
{this.state.n}
<button onClick = {this.onClick}>+1-1</button>
</div>
)
}
}
钩子执行顺序:
二. 函数组件
1. 创建方式
//第1种
function Welcome(props){
return <h1>hello,{props.name}</h1>;
}
//第2种
const Welcome = props=> <div>{props.name}<div/>
//第3种
const Welcome = (props)=> {return <div>{props.name}<div/>}
//使用方法
<Welcome name = "xiaoming"/>
2. React Hooks
2.1. useState
- 使用状态
const [n,setN] = useState(0)
const [user,setUser] = useState({name:"xiaohong"})
- setState(obj)不会自动合并属性;
- setState(obj) 如果obj地址没有变化,reac就认为地址没有变化
- useState()接受函数,该函数返回初始state,且只执行一次
const[state,setState] = useState(()=>{
return initialState
})
- setState()接受函数,优先使用函数
setN(i => i+1)
-
setN(n+1) 会产生一个新的n,原来的n并不会变
例: 先加1再log,log出的是1; 先log再加1 ,log的是原来的n ,即0
如果不希望出现分身,可以使用 useRef 或 useContext
function App(props){
const [n,setN] = useState(0)
const log =()=>{
setTimeOut(()=>{
console.log(`n:${n}`)
},3000)
}
return(
<div className = "app">
<p>{n}</p>
<p>
<button onClick = {() => setN(n+1)}>+1</button>
<button onClick = {log}>log</button>
</p>
</div>
)
}
- useState简单原理
//自己实现--原理
import React from 'react'
import ReactDom from 'react-dom'
const rootElement = document.getElementById("root")
let _state = []
let index = 0
fucntion myUseState(initialValue){
const currentIndex = index
index+=1
_state[currentIndex] = _state[currentIndex] || initialValue
const setState= (newState)=>{
_state[currentIndex] = newState;
render()
}
return[_state[currentIndex],setState]
}
const render=()=>{
index =0
ReactDom.render(<App />,rootElement)
}
function APP(props){
const [n,setN] = myUseState(0)
const [m,setM] = myUseState(0)
return(
<div className = "app">
<p>{n}</p>
<p>
<button onClick = {() => {setN(n+1)}}>+1</button>
</p>
<p>{m}</p>
<p>
<button onClick = {() => {setN(m+1)}}>+1</button>
</p>
<div>
)
}
ReactDom.render(<App />,rootElement)
2.2 useReducer
- 一. 创建初始值initialData
- 二. 创建所有操作reducer(state,action)
- 三. 传给useState,得到读和写API
- 四. 调用写API({type:"xxx"})
import React, { useState, useReducer } from "react";
import ReactDOM from "react-dom";
const initial = {
n: 0
};
const reducer = (state, action) => {
if (action.type === "add") {
return { n: state.n + action.number };
} else if (action.type === "multi") {
return { n: state.n * 2 };
} else {
throw new Error("unknown type");
}
};
function App() {
const [state, dispatch] = useReducer(reducer, initial);
const { n } = state;
const onClick = () => {
dispatch({ type: "add", number: 1 });
};
const onClick2 = () => {
dispatch({ type: "add", number: 2 });
};
return (
<div className="App">
<h1>n: {n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>+2</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2.3. useContext
- useContext不仅能贯穿始终,还能贯穿不同组件
- useContext不是响应式样的,在一个模块将themeContext的值改变,另一个模块不会感知到这个变化
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const rootElement = document.getElementById("root");
const themeContext = React.createContext(null);
function App() {
const [theme, setTheme] = React.useState("red");
return (
<themeContext.Provider value={{ theme, setTheme }}>
<div className={`App ${theme}`}>
<p>{theme}</p>
<div>
<ChildA />
</div>
<div>
<ChildB />
</div>
</div>
</themeContext.Provider>
);
}
function ChildA() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("red")}>red</button>
</div>
);
}
function ChildB() {
const { setTheme } = React.useContext(themeContext);
return (
<div>
<button onClick={() => setTheme("blue")}>blue</button>
</div>
);
}
ReactDOM.render(<App />, rootElement);
2.4. useRef & forwardRef
- 贯穿始终的状态
- useRef 不仅可以用于div,还可以用于任意数据
- 由于props种不包含ref,所以需要forwardRef
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");
function App() {
const nRef = React.useRef(0);
const update = React.useState()[1];
const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
return (
<div className="App">
<p>{nRef.current} 这里并不能实时更新</p>
<p>
<button onClick={() => {nRef.current += 1; update(nRef.current)}}>
+1
</button>
<button onClick={log}>log</button>
</p>
</div>
);
}
ReactDOM.render(<App />, rootElement);
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3>
</div>
);
}
const Button3 = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2.5. memo & useMemo & useCallback
- react 默认有多余的render
- 如果props不变,就没有必要再执行一次函数组件
- 当props中有函数时,就需要使用useMemo来支持函数重用
- useMemo第一个参数是 ()=>value,第二个参数是依赖[m,n]
- 只有当依赖发生变化时,才会计算新的value,不变就重用之前的value
- 如果value是个函数,就写成useMemo(()=>(x)=>{console.log(x)})
- useCallback((x)=>{console.log(x)},[m])等价于useMemo(()=>(x)=>{console.log(x)},[m])
import React, { useMemo } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [n, setN] = React.useState(0);
const [m, setM] = React.useState(0);
const onClick = () => {
setN(n + 1);
};
const onClick2 = () => {
setM(m + 1);
};
const onClickChild = useMemo(() => {
const fn = div => {
console.log("on click child, m: " + m);
console.log(div);
};
return fn;
}, [m]); // 这里呃 [m] 改成 [n] 就会打印出旧的 m
return (
<div className="App">
<div>
<button onClick={onClick}>update n {n}</button>
<button onClick={onClick2}>update m {m}</button>
</div>
<Child2 data={m} onClick={onClickChild} />
</div>
);
}
function Child(props) {
console.log("child 执行了");
console.log("假设这里有大量代码");
return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
2.6. useEffect & useLayoutEffect
- useEffect每次render后运行,(叫afterRender更好)
- useEffect在浏览器渲染完成以后执行,useLayoutEffect在浏览器渲染前执行
- useLayoutEffect总是比useEffect先执行
2.7. 自定义hook
//index.js
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";
function App() {
const { list, deleteIndex } = useList();
return (
<div className="App">
<h1>List</h1>
{list ? (
<ol>
{list.map((item, index) => (
<li key={item.id}>
{item.name}
<button
onClick={() => {
deleteIndex(index);
}}
>
x
</button>
</li>
))}
</ol>
) : (
"加载中..."
)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
//useList.js
import { useState, useEffect } from "react";
const useList = () => {
const [list, setList] = useState(null);
useEffect(() => {
ajax("/list").then(list => {
setList(list);
});
}, []); // [] 确保只在第一次运行
return {
list: list,
addItem: name => {
setList([...list, { id: Math.random(), name: name }]);
},
deleteIndex: index => {
setList(list.slice(0, index).concat(list.slice(index + 1)));
}
};
};
export default useList;
function ajax() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ id: "1", name: "Frank" },
{ id: "2", name: "Jack" },
{ id: "3", name: "Alice" },
{ id: "4", name: "Bob" }
]);
}, 2000);
});
}
5. 模拟声明周期
- 函数组件执行的时候,相当于执行constructor
- 函数组件的返回值就是render的返回值
- React.memo 和 React.useMemo可以模拟shouldComponentUpdate
useEffect(()=>{console.log("第一次渲染")},[]) //第一次渲染
useEffect(()=>{console.log("任意属性变化")}) //任意属性变化
useEffect(()=>{console.log("n变了")},[n]) //n变了
useEffect(()=>{
console.log("任意属性变化")
return ()=>{
console.log("组件要死了")
}
}) //组件要死了
三. eventHub
// 数据
var money = {
amount: 100000
}
var user = {
id: 123123,
nickname: '土豪'
}
var store = {
money: money,
user: user
}
// eventHub
var fnLists = {}
var eventHub = {
trigger(eventName, data){
let fnList = fnLists[eventName]
if(!fnList){return}
for(let i = 0; i<fnList.length; i++){
fnList[i](data)
}
},
on(eventName, fn){
if(!fnLists[eventName]){
fnLists[eventName] = []
}
fnLists[eventName].push(fn)
}
}
var x = {
init(){
eventHub.on('我想花钱', function(data){ // subscribe
store.money.amount -= data // reducer
render()
})
}
}
x.init()
class App extends React.Component {
constructor(){
super()
this.state = {
store: store
}
}
render(){
return (
<div className="root">
<BigPapa money={this.state.store.money} />
<YoungPapa money={this.state.store.money}/>
</div>
)
}
}
class BigPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="papa"> 大爸 {this.props.money.amount}
<Son1 money={this.props.money}/>
<Son2 money={this.props.money}/>
</div>
)
}
}
class YoungPapa extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="papa"> 二爸{this.props.money.amount}
<Son3 money={this.props.money}/>
<Son4 money={this.props.money}/>
</div>
)
}
}
class Son1 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子1 {this.props.money.amount}</div>
)
}
}
class Son2 extends React.Component{
constructor(){
super()
}
x(){
// action
eventHub.trigger('我想花钱' /*action type*/, 100) // payload
}
render(){
return (
<div className="son"> 儿子2 {this.props.money.amount}
<button onClick={()=>this.x()}>消费</button>
</div>
)
}
}
class Son3 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子3 {this.props.money.amount}</div>
)
}
}
class Son4 extends React.Component{
constructor(){
super()
}
render(){
return (
<div className="son"> 儿子4 {this.props.money.amount}</div>
)
}
}
function render(){
ReactDOM.render(<App/>, document.querySelector('#app'))
}