引入React:
* CDN引入:要引入两个库,一个是react.development
一个是react-dom.development
* umd引入
* webpack引入
cjs和umd的区别:
* cjs全称CommonJS,是Node.js支持的模块规范
* umd是统一模块定义,兼容各种模块规范(含浏览器)
* 理论上优先使用umd,同时支持Node.js和浏览器
* 最新的模块规范是使用import和export关键字
使用react:
1. yarn global add create-react-app
2. create-react-app reactDemo
3. yarn start
就会开启网页
普通代码和函数的区别:
* 普通代码立即求值,读取变量a的当前值
* 函数会等调用时再求值,即延迟求值。且求值时才会读取a的最新值
babel-loader内置了jsx内部的功能
使用jsx的注意事项
n
实际上是js代码,js中就是使用className,代码被转译成React.createElement('div',{className:'red'},'n')
return undefined
Vue和React写HTML的方式:
* Vue写在.vue文件的里
* React把HTML混在JS、JSX的文件里
条件判断
const Component = () => {
return n%2===0?<div>n是偶数</div>:<span>n是奇数<span>
}
如果外面需要div,也可以写成:
const Component = () => {
return (
<div>
{n%2===0?<div>n是偶数</div>:<span>n是奇数<span>}
</div>
)
}
在React中什么都是js
还可以写成:
const Component = () => {
const content = (
<div>
{n%2===0?<div>n是偶数</div>:<span>n是奇数<span>}
</div>
)
return content
}
还可以写成:
const Component = () => {
const inner = n%2===0?<div>n是偶数</div>:<span>n是奇数<span>
const content = (
<div>
{inner}
</div>
)
return content
}
还可以写成:
const Component = () => {
let inner
if(n%2===0){
inner = <div>n是偶数</div>
}else{
<span>n是奇数<span>
}
const content = (
<div>
{inner}
</div>
)
return content
}
循环语句:
const Component = (props) => {
return props.numbers.map((n,index)=>{
return <div>下标{index}的值为{n}</dev>
})
}
外面需要div
const Component = (props) => {
return (<div>
{props.numbers.map((n,index)=>{
return <div>下标{index}的值为{n}</div>
})}
</div>)
}
const Component = (props) => {
const array = []
for (let i = 0; i < props.numbers.length; i++) {
array.push(<div>下标{i}值为{props.numbers[i]}</div>)
}
return <div>{array}</div>
}
React.createElement创建虚拟DOM对象,函数的作用是多次创建虚拟DOM对象
DOM Diff就是两次组件有不同,对它进行比较不同
使用{}插入JS代码
Element(元素)VSComponent(组件):
如果一个函数返回了一个元素,那么它就叫做组件const Div = () => React.createElement('div'...)
下面是元素:const div = React.createElement('div'...)
React中有两种组件:
1. 函数组件
function Welcome(props) {
return <h1>Hello,{props.name}</h1>
}
使用方法:
props.name
是函数组件获取props数据的方式
2. 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello,{this.props.name}</h1>
}
}
使用方法:
]
render()
是用来渲染组件视图的
this.props.name
是类组件获取props数据的方式
React.createElement的逻辑:
组件之间的通信(外部数据对的使用):
不能对外部数据props进行写的操作,只能读
1. 函数组件给类组件传递:
父组件:
function App() {
const a = 0
return (
<div className="App">
爸爸 //传递的值是字符串就用""包起来,是JS中的表达式就用{ }包起来
<Son messasgeForSon={a}></Son>
</div>
)
}
子组件:
class Son extends React.Component {
render() {
return (
<div className="Son">
我是儿子爸爸对我说{this.props.messasgeForSon}
</div>
)
}
}
2. 类组件给函数组件传递:
父组件:
class Son extends React.Component {
render() {
return (
<div className="Son">
<GrandSon messasgeForGrandSon="小子你好"></GrandSon>
</div>
)
}
}
子组件:
const GrandSon = props => {
return (
<div className="GrandSon">
我是孙子,我爸对我说{props.messasgeForGrandSon}
</div>
)
}
类组件:
class Son extends React.Component {
constructor() {
super()
this.state = { //初始化内部数据
n: 0
}
}
add() {
this.setState({ n: this.state.n + 1 })//写内部数据,产生的是一个新的n,并没有修改原来的n
}
render() {
return (
<div className="Son">
儿子n:{this.state.n} //读取内部数据
<button onClick={() => this.add()}>+1</button>
</div>
)
}
}
类组件中如果setState只对其中的一部分进行改变,其他的部分会自动沿用之前的内容。
类组件的setState只会自动合并第一层的属性,并不会合并第二层的属性。
可以先把之前的复制过来再进行修改:
changeUser(){
this.setState(
user: {
...this.state.user,
name = "jack"
}
)
}
add(){
this.setState({ n: this.state.n + 1 })//这是一个异步更新UI的过程
console.log(n) //是+1之前的值
}
// 所以这样写不好,应该写成:
add(){
this.setState(state => {
const n = state.n + 1
console.log(n)
return { n }
})
}
函数组件:
const GrandSon = () => {
//初始值放在useState的括号里
const [n, setN] = React.useState(0) //两个值,一个用来读,一个用来写
return (
<div className="GrandSon">
孙子n:{n} //读
<button onClick={() => setN(n + 1)}>+1</button> //写,setN永远不会改变n,会产生一个新的n
</div>
)
}
如果把m,n放在一起,函数组件的setState不会自动合并,只要改了其中的一个,另一个值就会变成undefined,为了改变这种情况,应该先把之前的state进行拷贝:
<button onClick={() => setState(...state,n:state.n+1)}>+1</button>
最终的写法:
addN = () => this.setState({n: this.state.n +1})
<button onClick={()=>this.addN()}></button>
类内部自己的函数放在原型上,箭头函数放在本身的对象上面
两种创建class的方式:
import React from 'react'
const A = React.createClass({
render() {
return (
<div>hi</div>
)
}
})
export default A
import React from 'react'
class B extends React.Component {
constructor(props) { //初始化
super(props) //会把props内容放到this上面
}
render() {
return (){
<div>hi</div>
}
}
}
export default B
<B name="frank" onClick={this.onClick}>hi</B>
B组件接收到的props的值是{name:"frank", onClick:this.onClick, children: 'hi'}
如果内容不只是hi,children会被包装成数组
props的作用:
state(读)&setState(写)
初始化state:
class B extends React.Component {
constructor() {
super()
this.state = { //初始化内部数据
user: { name: 'frank', age: 18 }
}
}
render() { }
}
写的时候会shallow merge: setState会自动将新state与旧state进行一级合并
生命周期:
1. constructor() 在这里初始化state
2. static getDerivedStateFromProps()
3. shouldComponentUpdate() return false阻止更新
4. render() 创建虚拟DOM
5. getSnapshotBeforeUpdate()
6. componentDidMount() 组件已出现在页面
7. componentDidUpdate() 组件已更新
8. componentWillUnmount() 组件将死
9. static getDerivedStateFromError()
10. componentDidCatch
用途:
面试常问:
shouldComponentUpdate的作用:
React会将旧的虚拟DOM和新的虚拟DOM进行对比,没有改变的话就不会更新UI
可以将React.Component替换成React.PurComponent,会在render之前比较新旧state的不同,但它只会对比一层,是浅对比
useState的实现:
首次渲染:把虚拟div变为真实div的过程
1. setN
2.useState
3.x
尝试实现useState
// 让m和n不相互影响
let _state = []
let index = 0
const myUseState = initialValue => {
// 只能将index的值保存下来,对currentIndex进行操作,不然无法让index加一
const currentIndex = index
_state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]
const setState = newValue => {
_state[currentIndex] = newValue
render()
}
index += 1
return [_state[currentIndex], setState]
}
const render = () => {
// 在渲染组件之前将index的值重置,否则会一直往上加
index = 0
ReactDOM.render(< App />, rootElement)
}
但是上面这种方法出现两个问题:
App用了_state和index,其他组件就没得用了
解决方法:给每个组件创建一个_state和index
放在全局作=作用域中重名怎么办
解决方法:放在组件对应虚拟节点身上
总结:
React的Hooks:
1. 状态useState
2. 副作用useEffect
3. 上下文useContext
4. Redux useReducer
5. 记忆 useMemo
6. 引用 useRef
7. 自定义Hook
useState:
可以使用…user将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖
可以写成const [user,setUser] = useState(()=>({name:"lala",age:9*9}))
JS中必须要在对象外面包括号,才知道里面是对象。要把它变成函数是因为JS引擎在遇到函数的时候并不会立即执行,否则的话JS引擎在每一次遇到它的时候都会计算一次9*9,浪费时间。
由于setState是异步执行代码的,所以要把setN中的内容替换成函数,点击的时候会将x或者是n自动替换成n
useReducer:
const initial = {
n: 0
}
const reducer = (state, action) => {
if (action.type === 'add') {
return { n: state.n + action.number }
} else if (action.type === "mult") {
return { n: state.n * action.number }
} 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: 'mult', number: 2 })
}
return (
<div className="App">
<h1>n:{n}</h1>
<button onClick={onClick}>+1</button>
<button onClick={onClick2}>*2</button>
</div>
)
}
代码四步走:
1. 创建初始值initialState
2. 创建所有操作reducer(state,action)
3. 传给useReducer,得到读和写API
4. 调用写({type:“操作类型”})
useEffect第二个参数是空[]时,表示只会渲染一次 。
useReducer代替Redux:步骤:
1. 将数据集中在一个store对象
2. 将所有操作集中在reducer
3. 创建一个Context
4. 创建对数据的读写API
5. 将第四步的内容放到第三步的Context
6. 用Context.Provider将Context提供给所有组件
7. 各个组件用useContext获取读写API
import React, { createContext, useReducer, useContext, useEffect } from 'react';
import ReactDOM from 'react-dom';
//1. 将数据几种在一个store对象
const store = {
user: null,
books: null,
movies: null
}
//2.将所有操作集中在reducerconst
reducer = (state, action) => {
switch (action.type) {
case 'setUser':
return { ...state, user: action.user }
case 'setBooks':
return { ...state, books: action.books }
case 'setMovies':
return { ...state, movies: action.movies }
default:
throw new Error()
}
}
//3. 创建一个Contextconst
Context = createContext(null)function App() {
//4. 创建对数据的读写API
const [state, dispatch] = useReducer(reducer, store)
return (
// 第一个花括号表示里面的是JS内容,里面的花括号代表是对象,只是使用ES6的语法缩写了
//5. 将第四步的内容放到第三步的Context 6. 用Context.Provider将Context提供给所有组件
<Context.Provider value={{ state, dispatch }}>
<div>
<User />
<hr />
<Books />
<Movies />
</div>
</Context.Provider>)
}
function User() {
//7. 各个组件用useContext获取读写API
const { state, dispatch } = useContext(Context)
//useEffe第二个参数是空[]时,表示只会渲染一次
useEffect(() => {
ajax("/user").then(user => {
dispatch({ type: "setUser", user: user })
})
}, [])
return (
<div>
<h1>个人信息</h1>
<div>name:{state.user ? state.user.name : ""}</div>
</div>
)
}
function Books() {
const { state, dispatch } = useContext(Context)
useEffect(() => {
ajax("/books").then(books => {
dispatch({ type: "setBooks", books: books })
})
}, [])
return (
<div>
<h1>我的书籍</h1>
<ol>
{state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
</ol>
</div>
)
}
function Movies() {
const { state, dispatch } = useContext(Context)
useEffect(() => {
ajax("/movies").then(movies => {
dispatch({ type: "setMovies", movies: movies })
})
}, [])
return (
<div>
<h1>我的电影</h1>
<ol>
{state.movies ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>) : "加载中"}
</ol>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
function ajax(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path === "/user") {
resolve({
id: 1,
name: "lu"
})
} else if (path === "/books") {
resolve([{ id: 1, name: "JavaScript高级程序设计" },
{
id: 2,
name: "JavaScript精粹"
}
])
} else if (path === "/movies") {
resolve([
{
id: 1,
name: "送你一朵小红花"
},
{
id: 2,
name: "美人鱼"
}
])
}
}, 2000)
})
}
useContext
const Context = createContext(null)
function App(){
const [n,setN] = useState(0)
return (
<Context.Provider value={{n,setN}}>
<div className="App">
<h1>hello</h1>
<BaBa />
</div>
</Context.Provider>
)
}
function BaBa(){
return (
<div>我是爸爸<Child /></div>
)
}
function Child(){
const {n,setN} = useContext(Context)
const onClick = () => {
setN(n=>n+1)
}
return (
<div>我是儿子,我得到的n:{n}
<button onClick={onClick}>+1</button>
</div>
)
}
注意事项:不是响应式的,更新的机制是重新渲染
用途:
1. 作为componentDidMount使用,[]作第二个参数
2. 作为componentDidUpdate使用,可指定依赖
3. 作为componentWillUnmount使用,通过return
4. 以上三种用途可同时存在
特点:
如果同时存在多个useEffect,会按照出现次序执行
useEffect(() => {
console.log("第一次渲染后执行这一句话")
}, []) //数组是空时就表示不会再次执行,只在第一次渲染时执行,作为componentDidMount使用
useEffect(() => {
if(n!==0){
console.log("n变了")
}
},[n]) //[n]表示当n变化时再次执行代码,作为componentDidUpdate使用
useEffect(() => {
const id = setInterval(()=>{
console.log("hi")
},1000)
return ()=>{
window.clearInterval(id)
}
},[n]) //作为componentWillUnmount使用,通过return
useEffect(() => {
if(n!==0){
console.log("变了")
}
}) //什么都不写时代表不论什么变化了都会执行
useLayoutEffect(布局副作用)在浏览器渲染完成前执行
特点:
useLayoutEffect总是比useEffect先执行
更优先使用useEffect(优先渲染),使用useLayoutEffect相当于截胡,会延迟屏幕的渲染时间,影响用户体验DOM======>useLayoutEffec=====>渲染页面======>useEffectuseMemo:memo也可以避免重复渲染,但是由于一旦增加了监听函数之后就不行了,所以出现了useMemo,它可以实现函数的重用
useMemo有点像缓存,希望两次新旧组件迭代的时候使用上一次的值就可以使用它
const onClickChild = useMemo(() => {
return () => { }
}, [m]) //只有当m的值发生改变的时候返回函数的地址才会发生改变
特点:
注意:
使用useCallback改写上面的函数:
const onClickChild = useCallback(() => {
console.log(m)
}, [m])
目的:
自动刷新UI的方法:手动调用刷新
function App() {
const count = React.useRef(0)
const [_, set_] = React.useState(null)
const onClick2 = () => {
count.current += 1
set_(Math.random)
}
return (
<div className="App">
<button onClick={onClick2}>update count:{count.current}</button>
</div>
)
}
小结:
function App(){
const buttonRef = useRef(null)
return (
<div className="App">
<Button3 ref={buttonRef}>按钮</Button3 >
</div>
)
}
const Button2 = (props,ref) => {
return <button className="red" ref={ref} {...props} />
}
const Button3 = React.forwardRef(Button2) //将外面的ref转发给Button2 的第二个参数
自定义Hook:
const useList = () => {
const [list,setList] = useState(null)
useEffect(() => {
ajax("/list").then(list=>{
setList(list)
})},[])
return {
list,
setList
}
}
对上面Hook的使用:
function App(){
// buttonRef相当于DOM对象的引用
const {list} = useList()
return (
<div className="App">
<h1>List</h1>
{list?(<ol>{list.map(item=>(<li key={item.id}>{item.name}</li>))}</ol>):("加载中...")}
</div>
)
}
Stale Closure(过时闭包)
下面代码中的message和log就组成了一个过时的闭包
解决办法:坚持使用函数作为setState的参数,这样就不会受制于变量是新的还是旧的,因为传递的是一个动作
React中解决这个问题的办法:就是加一个依赖