引言
React,是Facebook开源的一个用于构建用户界面的JavaScript库,据说就和写js一样(我的js好像不太扎实QAQ),学了React后还可以用React Native来编写原生Android和IOS应用(据说挺热门的,准备下一步学这个)
React特点(抄的别人的)
- 声明式设计 −React采用声明范式,可以轻松描述应用。
- 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。(这里应该指的是虚拟DOM)
- 灵活 −React可以与已知的库或框架很好地配合。
- JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
- 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
- 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
HelloWorld
废话不多说,HelloWorld整起。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- React核心库 -->
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<!-- React操作DOM -->
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- Babel -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="test"></div>
<script type="text/babel">
const element = (
<div>
<h2>Hello World!</h2>
</div>
)
// 渲染
ReactDOM.render(element,document.getElementById("test"))
</script>
</body>
</html>
好大的HelloWorld(浏览器截图快捷键:Ctrl + Shift + X)
这里编写代码使用的语法是jsx(javascript的一种语法扩展),其实和js差不多,能在js中写HTML代码了,代码运行时会自动转成js代码
jsx中可以写表达式,但是不能写没有返回值的语句,例如:if,for
接下来鼠鼠就不说废话了,开始写!!!
列表渲染
<script type="text/babel">
const dataList = ["张三","李四","王五"]
const element = (
<div>
<h2>Hello World!</h2>
<ul>
{
/* 如果是首部插入元素,会导致全部元素重新渲染 */
dataList.map((item,index) => <li key={index}>{item}</li>)
}
</ul>
</div>
)
ReactDOM.render(element,document.getElementById("test"))
</script>
手动创建项目
先准备好项目结构
然后依次执行以下命令:
- npm init -y (会生成package.json文件)
- npm install react react-dom react-scripts -S(react相关依赖)
然后在package.json文件中添加以下内容:
"start": "react-scripts start",
然后输入npm start或者点击启动符号就可以启动了。
脚手架创建项目
先用create-react-app创建一个项目:
要用管理员启动命令行,node版本也有要求
脚手架创建的项目结构:
组件
React中定义组件的方式有两种:函数式组件和类式组件。
函数式组件:
function App() {
return (
<div>
<h2>Hello World!</h2>
</div>
);
}
export default App;
类式组件:
import './App.css';
import {Component} from "react";
class App extends Component{
render(){
return (
<div>
<h2>Hello World!</h2>
</div>
);
}
}
export default App;
引入其他组件:
src/App.js
import './App.css';
import React, {Component} from "react";
import Child1 from "./components/child1";
class App extends Component{
render(){
return (
<div>
<Child1 />
</div>
);
}
}
export default App;
components/child1/index,jsx
const Child1 = () => {
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
</>
)
}
export default Child1
事件
React中的事件和HTML中的事件很相似,React中事件采用的是驼峰命名法。
React事件属性需要传递的是函数对象,而不是函数的返回值
const Child1 = () => {
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
return (
<>
<input type="text" onBlur={handleClick} />
</>
)
}
export default Child1
如果需要传参数:
const Child1 = () => {
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
</>
)
}
export default Child1
props
props用于父子组件传递参数:
App.js
import './App.css';
import React, {Component} from "react";
import Child1 from "./components/child1";
class App extends Component{
render(){
return (
<div>
<Child1 userInfo={{
name: "张三",
age: 18,
sex: "男"
}} />
</div>
);
}
}
export default App;
Child1/index.jsx
const Child1 = (props) => {
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
const {userInfo} = props
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
<hr/>
<span>{`${userInfo.name},${userInfo.age},${userInfo.sex}`}</span>
</>
)
}
export default Child1
props中的属性是只读的,无法修改
state
state一般用于存储和视图有关联的数据,数据驱动视图发生变化,每个组件都有他自己的state。
import {useState} from "react";
const Child1 = (props) => {
const {userInfo} = props
const [date,setDate] = useState(new Date().toLocaleTimeString())
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
const refreshDate = () => {
setDate(new Date().toLocaleTimeString())
}
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
<hr/>
<span>{`${userInfo.name},${userInfo.age},${userInfo.sex}`}</span>
<hr/>
<span>最新时间:{date}</span>
<button onClick={refreshDate}>刷新</button>
</>
)
}
export default Child1
点击按钮可以发现时间发生变化了。
但是如果修改date的这个方法中涉及到一些异步请求之类的逻辑,可能会得到意想不到的结果。
例如:
import {useState} from "react";
const Child1 = (props) => {
const {userInfo} = props
const [date,setDate] = useState(new Date().toLocaleTimeString())
const [count,setCount] = useState(0)
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
const refreshDate = () => {
setDate(new Date().toLocaleTimeString())
}
const incrementCount = () => {
console.log("点击了")
setTimeout(() => {
setCount(count + 1)
},1000)
}
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
<hr/>
<span>{`${userInfo.name},${userInfo.age},${userInfo.sex}`}</span>
<hr/>
<span>最新时间:{date}</span>
<button onClick={refreshDate}>刷新</button>
<hr/>
<span>当前值:{count}</span>
<button onClick={incrementCount}>+</button>
</>
)
}
export default Child1
可以发现我一秒钟内点击了3次,但是值却只增加了1,当需要用到之前的值时,我们可以换一种写法。
const incrementCount = () => {
console.log("点击了")
setTimeout(() => {
setCount(preCount => preCount + 1)
},1000)
}
可以发现现在点击增加的值是正常的。
ref
ref为我们提供了一种操作DOM的方式
import {useState,useRef} from "react";
const Child1 = (props) => {
const {userInfo} = props
const [date,setDate] = useState(new Date().toLocaleTimeString())
const [count,setCount] = useState(0)
const inputRef = useRef()
const handleClick = (e) => {
console.log("你干嘛~",e.target.value)
}
const handleButtonClick = (str) => {
return (e) => {
console.log(e.target,str)
}
}
const refreshDate = () => {
setDate(new Date().toLocaleTimeString())
}
const incrementCount = () => {
console.log("点击了")
setTimeout(() => {
setCount(preCount => preCount + 1)
},1000)
}
const handleBlur = () => {
console.log(inputRef.current.value)
}
return (
<>
<input type="text" onBlur={handleClick} />
<button onClick={handleButtonClick("你干嘛")}>点我</button>
<hr/>
<span>{`${userInfo.name},${userInfo.age},${userInfo.sex}`}</span>
<hr/>
<span>最新时间:{date}</span>
<button onClick={refreshDate}>刷新</button>
<hr/>
<span>当前值:{count}</span>
<button onClick={incrementCount}>+</button>
<hr/>
<input ref={inputRef} type="text" onBlur={handleBlur}/>
</>
)
}
export default Child1
非必要的情况下,最好不要操作原生DOM对象
form
表单处理:
import React, {useState} from "react";
const Form = () => {
const [form,setForm] = useState({
userName: "",
password: ""
})
const setUserName = (e) => {
setForm(Object.assign(form,{userName: e.target.value}))
}
const setPassword = (e) => {
setForm(Object.assign(form,{password: e.target.value}))
}
const handleSubmit = (e) => {
console.log(form)
e.preventDefault()
}
return (
<>
<form onSubmit={handleSubmit}>
用户名:<input type="text" onChange={setUserName} />
<br/>
密码:<input type="text" onChange={setPassword} />
<input type="submit" value="提交" />
</form>
</>
)
}
export default Form
css样式
import React, {useState} from "react"
const Weather = () => {
const [isHot,setWeather] = useState(true)
return (
<>
<span style={{backgroundColor: isHot ? "red" : "green"}}>当前天气:{isHot ? "炎热" : "寒冷"}</span>
<button onClick={() => {setWeather(!isHot)}}>点我</button>
</>
)
}
export default Weather
css模块化引入:必须以.module.css
结尾
weather.module.css
.weather {
font-size: 25px;
}
index.jsx
import React, {useState} from "react"
import weather from "./weather.module.css"
const Weather = () => {
const [isHot,setWeather] = useState(true)
return (
<>
<span className={weather.weather} style={{backgroundColor: isHot ? "red" : "green"}}>当前天气:{isHot ? "炎热" : "寒冷"}</span>
<button onClick={() => {setWeather(!isHot)}}>点我</button>
</>
)
}
export default Weather
context
react还为我们提供了一种组件之间的通信方式,只需要在最外层的组件设置了属性,内层所有子组件都可访问到。
context/index.jsx
import {createContext} from "react";
// 用来创建context,可以传入一个参数,用来指定初始值
const TestContext = createContext()
export default TestContext
useContext:传入Context对象,可以获得Context对象存储的数据
son/index.jsx
import React, {useContext} from "react"
import TestContext from "../../../../store/context/TestContext";
const Son = () => {
const ctx = useContext(TestContext)
return (
<>
<span>我是Son,{`${ctx.firstName},${ctx.location}`}</span>
</>
)
}
export default Son
father/index.jsx
import React, {useContext} from "react"
import Son from "./son";
import TestContext from "../../../store/context/TestContext";
const Father = () => {
const ctx = useContext(TestContext)
return (
<>
<span>我是Father,{`${ctx.firstName},${ctx.location}`}</span>
<hr/>
<Son />
</>
)
}
export default Father
grandPa/index.jsx
import React, {useContext} from "react"
import Father from "./father";
import TestContext from "../../store/context/TestContext";
const GrandPa = () => {
const ctx = useContext(TestContext)
return (
<>
<span>我是GrandPa,{`${ctx.firstName},${ctx.location}`}</span>
<hr/>
<Father />
</>
)
}
export default GrandPa
App.js
import './App.css';
import React, {Component} from "react";
import Child1 from "./components/child1";
import Form from "./components/form";
import Weather from "./components/weather";
import GrandPa from "./components/grandPa";
import TestContext from "./store/context/TestContext";
class App extends Component{
render(){
return (
<div>
<Child1 userInfo={{
name: "张三",
age: 18,
sex: "男"
}} />
<hr/>
<Form />
<hr/>
<Weather />
<hr/>
<TestContext.Provider value={{firstName: "欧阳",location: "鵗"}}>
<GrandPa />
</TestContext.Provider>
</div>
);
}
}
export default App;
useEffect
useEffect方法会在每次DOM渲染完毕之后执行,这里可以写一些渲染之后需要处理的业务逻辑。
import React, {useContext, useEffect, useState} from "react"
import TestContext from "../../../../store/context/TestContext";
const Son = () => {
const ctx = useContext(TestContext)
const [count,setCount] = useState(0)
useEffect(() => {
// 每次组件渲染之后执行
console.log("DOM渲染完了")
})
return (
<>
<span>我是Son,{`${ctx.firstName},${ctx.location}`}</span>
<span>count:{count}</span>
<button onClick={() => {setCount(count + 1)}}>+</button>
</>
)
}
export default Son
如果前后两次执行effect会互相影响,就需要清除之前effect带来的影响:而且不是每次更新都要调用effect方法,第二个参数指定只有那些数据发生变化才会调用useEffect方法
import React, {useContext, useEffect, useState} from "react"
import TestContext from "../../../../store/context/TestContext";
const Son = () => {
const ctx = useContext(TestContext)
const [count,setCount] = useState(0)
const [num,setNum] = useState(0)
useEffect(() => {
console.log("useEffect")
const timer = setInterval(() => {
// 每次组件渲染之后执行
console.log("DOM渲染完了")
},1000)
return () => {
// 清除之前effect造成的影响
clearInterval(timer)
}
// 设置只有num参数变化才调用effect方法
},[num])
return (
<>
<span>我是Son,{`${ctx.firstName},${ctx.location}`}</span>
<span>count:{count}</span>
<button onClick={() => {setCount(count + 1)}}>+</button>
<span>count:{num}</span>
<button onClick={() => {setNum(num + 1)}}>+</button>
</>
)
}
export default Son
reduer
之前修改数据都是setState,对于一些简单地数据还好,当数据比较复杂时,可以使用Reducer
import React, {useReducer} from "react"
const userReducer = (state,action) => {
switch (action.type) {
case "userName":
return {
...state,
userName: state.userName = "李四"
}
case "password":
return {
...state,
password: state.password = "QWERTY"
}
default:
return {}
}
}
const TestReducer = () => {
const [userInfo,dispatch] = useReducer(userReducer,{
userName: "张三",
password: "123456"
})
return (
<>
<span>{userInfo.userName} = {userInfo.password}</span>
<input type="text" onBlur={() => {dispatch({type:"userName"})}} />
<input type="password" onBlur={() => {dispatch({type: "password"})}} />
</>
)
}
export default TestReducer
memo
当父组件重新渲染时,对应的子组件也会重新渲染,循环往下,但是有时候,只有父组件发生了变化,子组件和渲染之前并无差别,那么这时候子组件没有必要重新渲染,React为我们提供了可以缓存
组件的选择。
export default React.memo(Father)
fetch
fetch是浏览器自带的一种发送请求的方式,不用引入任何库
import React, {useContext, useEffect} from "react"
import Son from "./son";
import TestContext from "../../../store/context/TestContext";
const Father = () => {
const ctx = useContext(TestContext)
useEffect(() => {
fetch("http://localhost:3000/user/student")
.then(res => res.json())
.then(data => {
console.log("data",data)
})
})
return (
<>
<span>我是Father,{`${ctx.firstName},${ctx.location}`}</span>
<hr/>
<Son />
</>
)
}
export default React.memo(Father)
可以发现请求到数据了
自定义钩子
有时候我们需要根据公司业务去定制化一些可复用的代码,react允许我们提供了自定义钩子,需要满足三个要求:
- 以use开头
- 正常调用react的其他钩子
- 在其他组件中引用钩子
import React, {useState} from "react"
const useFetch = (url,requestArgs = {}) => {
// 数据Loading
const [loading,setLoading] = useState(false)
// 请求数据
const [data,setData] = useState()
// 错误信息
const [err,setErr] = useState(null)
async function fetchData(){
try {
setLoading(true)
setErr(null)
const res = await fetch(url,requestArgs)
if(!res.ok){
throw new Error("未知错误,请反馈管理员")
}
const data = await res.json()
setData(data)
}catch (e) {
setErr(e)
}finally {
setLoading(false)
}
}
return {loading,data,err,fetchData}
}
export default useFetch
在组件中引用:
import React, {useContext, useEffect} from "react"
import Son from "./son";
import TestContext from "../../../store/context/TestContext";
import useFetch from "../../../hooks/useFetch";
const Father = () => {
const ctx = useContext(TestContext)
const {data,loading,err,fetchData} = useFetch("http://localhost:3000/user/student")
useEffect(() => {
fetchData()
},[])
// 只运行一次,不添加就会一直无限调用
return (
<>
<span>我是Father,{`${ctx.firstName},${ctx.location}`}</span>
<hr/>
<Son />
</>
)
}
export default React.memo(Father)
依然能正常访问到
redux
老版使用方式:
下载依赖:
npm install -S redux react-redux
carReducer/index.jsx
const catReducer = (state = {
name: "阿猫",
age: 1
},action) => {
switch (action.type) {
case "SET_NAME":
return {
...state,
name: action.payload
}
case "SET_AGE":
return {
...state,
age: state.age + action.payload
}
default:
return {
...state
}
}
}
export default catReducer
reactReducer/index.jsx
import React from "react"
import {useDispatch, useSelector} from "react-redux";
const TestReactReducer = () => {
const state = useSelector(state => state)
const dispatch = useDispatch()
return (
<>
<span>我是{state.name},我今年{state.age}岁了</span>
<button onClick={() => {dispatch({type:"SET_NAME",payload:"大猫"})}}>改名字</button>
<button onClick={() => {dispatch({type:"SET_AGE",payload:1})}}>长大</button>
</>
)
}
export default TestReactReducer
App.js
import './App.css';
import React, {Component} from "react";
import Child1 from "./components/child1";
import Form from "./components/form";
import Weather from "./components/weather";
import GrandPa from "./components/grandPa";
import TestContext from "./store/context/TestContext";
import TestReducer from "./components/testReducer";
import TestReactReducer from "./components/reactReducer"
import catReducer from "./components/catReducer";
import {Provider, useSelector} from "react-redux";
import {createStore} from "redux";
// 老版创建store方式
const store = createStore(catReducer)
store.subscribe(() => {
console.log("state发生变化了 ")
})
class App extends Component{
render(){
return (
<div>
<Child1 userInfo={{
name: "张三",
age: 18,
sex: "男"
}} />
<hr/>
<Form />
<hr/>
<Weather />
<hr/>
<TestContext.Provider value={{firstName: "欧阳",location: "鵗"}}>
<GrandPa />
</TestContext.Provider>
<hr/>
<TestReducer />
<Provider store={store}>
<TestReactReducer />
</Provider>
</div>
);
}
}
export default App;
如果是多个reduer:
dogReducer/index.jsx
const dogReducer = (state = {
name: "阿狗",
age: 2
},action) => {
switch (action.type) {
case "SET_DOG_NAME":
return {
...state,
name: action.payload
}
case "SET_DOG_AGE":
return {
...state,
age: state.age + action.payload
}
default:
return {
...state
}
}
}
export default dogReducer
reactReducer/index.jsx
import React from "react"
import {useDispatch, useSelector} from "react-redux";
const TestReactReducer = () => {
const cat = useSelector(state => state.cat)
const dog = useSelector(state => state.dog)
const dispatch = useDispatch()
return (
<>
<span>我是{cat.name},我今年{cat.age}岁了</span>
<button onClick={() => {dispatch({type:"SET_CAT_NAME",payload:"大猫"})}}>改名字</button>
<button onClick={() => {dispatch({type:"SET_CAT_AGE",payload:1})}}>长大</button>
<hr/>
<span>我是{dog.name},我今年{dog.age}岁了</span>
<button onClick={() => {dispatch({type:"SET_DOG_NAME",payload:"大狗"})}}>改名字</button>
<button onClick={() => {dispatch({type:"SET_DOG_AGE",payload:2})}}>长大</button>
</>
)
}
export default TestReactReducer
App.js
const reducers = combineReducers({
cat: catReducer,
dog: dogReducer
})
const store = createStore(reducers)
store.subscribe(() => {
console.log("state发生变化了 ")
})
router
通过路由,我们可以做到让url地址映射react组件。
安装依赖:
npm install react-router-dom@5 -S
import {Link,Route} from "react-router-dom";
<Link to="/about">About</Link>
<br/>
<Link to="/home">Home</Link>
<hr/>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
path可以匹配多个url:
<Link to="/about1">About1</Link>
<br/>
<Link to="/about2">About2</Link>
<br/>
<Link to="/home">Home</Link>
<hr/>
<Route path={["/about1","/about2"]} component={About} />
<Route path="/home" component={Home} />
传递参数:
<Link to="/about1/1">About1</Link>
<br/>
<Link to="/about2">About2</Link>
<br/>
<Link to="/home">Home</Link>
<hr/>
<Route path={["/about1/:id","/about2"]} component={About} />
<Route path="/home" component={Home} />
about/index.jsx
import React from "react"
import {useParams} from "react-router-dom"
const About = () => {
const {id} = useParams()
console.log("接收到id:" + id)
return (
<>
<h2>我是About</h2>
</>
)
}
export default About
可以发现接收到了。
react-router-dom@6版本:
npm install react-router-dom@6 -S
<Link to="/about">About</Link>
<br/>
<Link to="/home">Home</Link>
<br/>
<hr/>
<Routes>
<Route path={"/"} element={<About />} />
<Route path={"/about"} element={<About />} />
<Route path="/home" element={<Home />} />
</Routes>
嵌套路由:
App.js
<Link to="/about">About</Link>
<br/>
<Link to="/about/123">AboutChild</Link>
<br/>
<Link to="/home">Home</Link>
<br/>
<hr/>
<Routes>
<Route path={"/"} element={<About />} />
<Route path={"/about"} element={<About />}>
<Route path=":id" element={<AboutChild />} />
</Route>
<Route path="/home" element={<Home />} />
</Routes>
about/child/index.jsx
import React from "react"
import {useParams} from "react-router-dom"
const AboutChild = () => {
const {id} = useParams()
console.log("child接收到id:" + id)
return (
<>
<h2>我是AboutChild</h2>
</>
)
}
export default AboutChild
结语:学到这里纯属爱好,有空再更新!