文章目录
- 1. 创建项目启动服务
- 2. ReactDOM.render()返回值
- 3. 原生JS与React语法糖写法
- 4. JSX变量引入
- 5. Reatc默认导出 命名导出
- 6. 函数组件
- 7. React生命周期
- demo案例
- 8.关于字符串判断初始字符
- 9.类组件中this指向
- 10.关于函数柯里化
- 11. 关于事件绑定函数写法
- 12.关于React中的Refs使用方法
- 13. 记事本便签案例【考查受控组件及非受控组件】
- 14. 关于React中img引入
- 15. 关于React中行内样式引入
- 16. 行内引入html代码dangerouslySetInnerHTML
- 17.Hook相关使用
- 18. 关于memo、useCallback等优化操作
- 19. React路由
- 20. 关于获取属性判空的两种写法
- 21. 权限路由
- 22. 关于自定义hook组件
- 23.redux组件
1. 创建项目启动服务
创建项目
- 本地创建workspace文件夹
- 在workspace文件夹打开终端
npx create-react-app demo(新建项目名称)
【注意此处为npx 切勿误写npm】
cd demo(新建项目名称)
【进入新建文件夹】
npm start
【启动服务】
启动项目
- 项目已创建好再次打开时 只需
cd demo
npm start
即可- 项目
src
路径的index.js为入口文件app.js
为外部导入文件
2. ReactDOM.render()返回值
ReactDOM.render(
<div>hello world</div>,
document.getElementById("root")
)
render内只能有两块内容作为参数 若有多块内容 则需要用整体一个div盒子包裹起来
ReactDOM.render(
<div>
<h1>我是多个内容的第一个</h1>
<div>我是多个内容的第二个</div>
</div>,
document.getElementById("root")
)
3. 原生JS与React语法糖写法
const ele1=React.createElement(//原生JS写法
'div',
{id:'box'},
'hello',
React.createElemen('h1',null,'React')
)
const ele=(//React语法糖写法
<div>
hello
<h1>React</h1>
</div>
)
-
babel工具帮助把简洁的语法糖形式转换为原生JS函数进行调用 所以我们只需要按照语法糖形式敲代码即可
-
通过控制台打印ele 可以看到返回值为对象形式 此为JSX对象
4. JSX变量引入
const str='react'
const element=(
<div className="title">//JSX的class属性写做className形式
{str}
</div>
)
ReactDOM.render(
<div>
<h1>hello</h1>
{element}
</div>,
document.getElementById("root")
)
5. Reatc默认导出 命名导出
- 默认导出【
只有一个默认导出
】
//render.js 需要导入的模块为render.js
function hello(){
return(//函数必须有renturn及返回值
<div>我是导入模块</div>
)
}
//导出语句
export default hello//此处默认导出的名字需要与定义的函数一致
//index.js 导入至index.js模块
//通过import引入后可通过【指定名字(eg:bye)此名字与具体函数无关 随意命名】 调用函数
import bye from"./render"//from+路径【路径:为导入文件所在位置的相对路径】
ReactDOM.render(
<bye/>,
document.getElementById("root")
)
- 命名导入【
可以有多个
】
//render.js
export const A=42
export const B=()=>{
return(
<div>我是命名导入模块</div>
)
}
//index.js
import {A,B}from'./render'//从render中导入特定名字为A和B的变量及函数
ReactDOM.render(
{A}//命名导入变量通过 {变量名}引入
<B/>,//命名导入函数通过<函数名/>引入
document.getElemntById("root")
)
6. 函数组件
- 类组件
类组件的名字必须为大写
类组件必须有render方法 类组件的每一次变量、方法引用都需要用this
类组件 的变量初始化等都放在this.state中
类组件中的方法尽量使用箭头函数 防止因this指向不明导致获取数据不准确
class Content extends React.Component{
constructor(props){
super(props);
this.state={
title:"列表标题",
time:new Date().toLocalTimerString()
}
setInterval(()=>{
this.setState({time:new Date().toLocalTimerString()})
},1000)
}
render(){
return(
<div>
<div>
{this.state.time}</div>
<div>
{this.props.data}
</div>
</div>
)
}
}
ReactDOM.render(){
<div>
<Content data={['a','b','c']} />
</div>
}
super() 括号内不加任何实参 下边代码只能拿到this ;加实参props可以拿到this.props 一般情况可以不加
- 函数组件
const Content=(props)=>{//通过传参获取render函数返回的data全部数据
return(
<div>我是函数组件</div>
<ul>
{
props.data.map(item=>(
<li key={item}>{item}</li>
)
//箭头函数两种表达式
//props.data.map((item)=>{return <li>{item}</li>})
}
</ul>
)
}
ReactDOM.render(){
<div>
<Content data={['a','b','c']} />
</div>
}
7. React生命周期
学习至原创https://www.jianshu.com/p/e3d1ecfb6312
生命周期指语言或某个组件 从创建至销毁的过程 React通过定义几个函数来控制组件在生命周期每个阶段的功能
- mountig
① constructor():构造函数【组件创建时调用】
② componentWillMount():组件挂载之前调用 加载loading等操作
③ render():有返回值return 返回React元素 只能纯洁的渲染;可以不返回任何值return null
④ componentDidMount():组件挂载之后访问 用于耗时请求服务器或定时器【首次render()之后调用】
- updating【更新】【更新阶段生命周期函数不要定义 定时器】
① componentWillReceiveProps(nextProps):父组件发生render时就会调用子组件 【props是否发生变化影响不大,因为总会随着父组件render子组件进行componentWillReceiveProps操作】【在这个回调函数中,如果父组件发生变化,可以根据属性的变化,调用this.setState()来更新组件状态】【不会触发额外的render调用】
② shouldComponentUpdate():组件挂载之后、渲染前调用 可以通过返回值的true或false判断是否重新渲染组件【处理只是数据改变、页面不改变的情况,用来优化渲染效率】
shouldComponentUpdate(){
console.log("shouleComponentUpdate");
return true;//返回true才能保证后续渲染成功进行 即通过自定义布尔值实现是否被调用
}
③ componentWillUpdate(nextProps,nextState):组件被更新前调用 render方法之前进行 也会事先判断shouldComponentUpdate()的返回值是否为true【这个方法不能用this.setState方法来修改状态 函数调用之后会修改nextProps、nextState的值到this.props和this.nextState中】
④ render():
⑤ componentDidUpdate():组件渲染后调用 首次render不会调用 即更新完成后执行此状态代码 其执行且只执行一次 所以每次执行都会拿到最新数据 ,所以直接stringfy(this.state.list)即可 无需考虑setState的异步形式
componentDidUpdata(){
localStorage.setItem('todolist',JSON.stringfy(this.state.list))
}
- unmounting【卸载】
① componentWillUnmount():组件被卸载时调用 取消定时器 网络请求等
demo案例
定时执行
//index.js入口文件
setInterval(()=>{
const ele=(
<h1>一级标题</h1>
<div>{new Date().toLocalTimerString()}</div>
)
ReactDOM.render(
<div>
{ele}
</div>,
document.getElementById("root")
)
},1000)
调接口页面挂载数据
//App.js
import React from'react'
//定义类组件
class Content extends React.Component{
constructor(props){
//初始化类组件
super(props);//继承父元素的props
this.state={data:[]}//将自身类组件的状态设置为data数组形式
}
render(){
return(
<div>{//渲染数据为遍历data数组中元素
this.state.data.map(item=><li key={item.id}>{item.title}</li>)
//关于遍历时的索引值:逆序添加、删除时,不能用索引值index做key值;顺序添加、顺序删除时可以用index做索引值;有id值最好用id值,可以唯一判断标识
}</div>
)
}
}
componentDidMount(){//挂载成功之后执行且一次 请求服务器
//页面渲染成功后再通过fetch【js原生调数据的方法】获取数据并重新设置渲染元素状态
fetch("https://cnodejs.org/api/v1/topics").then(res=>res.json())
.then(res=>{
this.setState({data:res.data})
})
}
function App(){
return(
<Content/>
)
}
export default APP
//index.js
import App from"./App"
ReactDOM.render(
<App/>,
document.getElementById("root");
)
-
获取接口数据显示在界面上
- react写法
constructor(){
super();
this.state = {
time: new Date().toLocaleTimeString(),
topics: []
}
}
componentDidMount() {// 只执行一次的生命周期
fetch('https://cnodejs.org/api/v1/topics')
.then(res=>res.json())
.then(res=>{
// React写法,很简单
console.log(res.data);
this.setState({topics:res.data});
})
setInterval(()=>{
this.setState({time:new Date().toLocaleTimeString()})
},1000)
}
render(){
const {time,topics} = this.state;
return <div>
<h1>{time}</h1>
<h2>共有 {topics.length} 条帖子</h2>
<ul>
{
topics.map( topic=>{
return <li key={topic.id}>
<img style={ {width: 80} } alt='' src={topic.author.avatar_url}/>
<span>{topic.reply_count}</span>
{topic.title}
<span>{changeTime(topic.last_reply_at)}</span>
</li>
} )
}
</ul>
</div>
}
- 原生js写法
constructor(){
super();
this.state = {
time: new Date().toLocaleTimeString(),
topics: []
}
}
componentDidMount() {// 只执行一次的生命周期
fetch('https://cnodejs.org/api/v1/topics')
.then(res=>res.json())
.then(res=>{
// React写法,很简单
console.log(res.data);
let list=document.getElementById("list")
for(let i=0;i<res.data.length;i++){
let li=document.createElement("li")
li.innerHTML='<img src='+res.data[i].author.avatar_url+list.appendChild(li);
}
document.querySelector('h2').innerHTML=res.data.length
})
setInterval(()=>{
this.setState({time:new Date().toLocaleTimeString()})
},1000)
}
render(){
const {time,topics} = this.state;
return <div>
<h1>{time}</h1>
<h2>共有 {topics.length} 条帖子</h2>
<ul id="list"></ul>
</div>
}
自定义render方法代替ReactDOM
//index.js 入口文件
import React from 'react'
import {render}from"./App"
const str='react'
const element=(
<div>
我是语法糖jsx写法
{str}
</div>
)
const ele=<h1 id="box">hello啦</h1>
const e=(
<div>
<h2>我是二级标题啦</h2>
hello React
{element}
{ele}
{str}
</div>
)
render(e,document.getElementById("root"));
//App.js入口
export const render=(element,container)=>{
//解构取值 获取到当前html中结构 并赋值到type与props中
const{type,props}=element;//type 为最外层盒子的标签元素 props为盒子的属性及内容
const el=document.createElement(type);//创建最外层标签的结点元素
container.appendChild(el);//向父元素添加创建的结点
Object.keys(props).forEach(attr=>{//遍历循环props中的元素
if(attr==="children"){
const child=props['children']
if(typeof child=="string"){//说明最外层元素内只有字符串 没有内含标签
el.innerText=child;
}else if(child instanceof Array){//说明最外层元素的内含有独立不止一个元素 则遍历这个数组查看内置元素形式
child.forEach(item=>{
if(typeof item=="string"){
const txtNode=document.createTextNode(item);//根据item元素创建文本结点
el.appendChild(txtNode);
}else{
render(item,el);
}
})
}else{
render(child,el);
}
}else{//为属性结点时则将其属性及属性值添加给父元素
el[attr]=props[attr]
}
})
}
8.关于字符串判断初始字符
同样可以通过数组形式判断字符串的第一个字符形式
var props='#root'
if(props[0]=='#'){
console.log("我是class值")
}
9.类组件中this指向
两个①的this、add指向同一个;两个②的this、add指向同一个
export default class demo extends Component{
constructor(){
super();
this.state={
num:0
}
①this.add=this.add.bind(this)②
}
②var add=function(){
this.setState({
num:this.state.num+1
})
}
render(){
return(
<div>
<h2>{this.state.num}</h2>
<button onClick={this.add()①}>点我+1</button>
</div>
)
}
}
改进:使用箭头函数声明事件函数 可以省去事件绑定 且this指向不出问题
export default class demo extends Component{
constructor(){
super();
this.state={
num:0
}
}
add=()=>{
this.setState({
num:this.state.num+1
})
}
render(){
return(
<div>
<h2>{this.state.num}</h2>
<button onClick={this.add()}>点我+1</button>
</div>
)
}
}
10.关于函数柯里化
const a=a=>b=>c=>a+b+c
const add1=(a,b,c)=>{
return a+b+c
}
//由add1向add2的转变极为函数柯里化
const add2=(a)=>{
return(b)=>{
return(c)=>{
return a+b+c
}
}
}
console.log(add1(1,2,3))
console.log(add2(1)(2)(3))
11. 关于事件绑定函数写法
未涉及事件对象
- 直接箭头函数绑定
add=(type)=>{
this.setState({
[type]:this.state[type]+1//用[]括起传入参数 可以保证为引入变量修改状态 而不是重新设置名为type的变量
})
}
<button onClick={()=>this.add('num')}>支持</button>
- 给函数添加返回值 后边可以直接引用函数【柯里化形式】
<button onClick={this.add('num')}>支持</button>
add=(type)=>{
return()=>{
this.setState({
[type]:this.state[type]+1
})
}
}
涉及事件对象形式
- 直接箭头函数绑定
state={
inp:''
}
inpChange=(type,e)=>{
this.setState({
[type]:e.target.value
})
}
render(){
return(
<div>
<input value={this.state.inp} onChange={(e)=>{this.inpChange('inp',e)}}/>
</div>
)
}
- 柯里化形式
inpChange=(type)=>{
return(e)=>{
this.setState({
[type]:e.target.value
})
}
}
render(){
return(
<div>
<input value={this.state.inp} onChange={this.inpChange('inp')}/>
</div>
)
}
12.关于React中的Refs使用方法
Refs提供允许访问DOM节点或在render方法中创建React元素
- 方法一 ref属性 直接获取节点
import {Component}from 'react'
export default class Learn extends Compenent{
render(){
return(
<div>
<input ref='inp'/>
</div>
)
}
componentDidMount(){
console.log(this)//通过打印可知其为本函数的实例对象
console.log(this.refs.inp)//根据refs查找其值为inp的属性 获取对应的节点
this.refs.inp.focus()//直接获取节点 并调用方法【实现效果为:页面加载时即获得其光标键入】
}
}
- 方法二 ref的回调函数用法
import {Component} from 'react'
export default class Learn extends Component{
getNode=(node)=>{
console.log(node)//通过打印可知为获得的input节点
let input=node//重新定义变量获取节点
node.focus()//调用方法实现对节点的处理
}
render(){
return({
<div>
<input ref={this.getNode}/>
</div>
})
}
}
- 方法三 createRef方法:通过React.createRef()创建Refs并通过ref属性联系到React组件
import {Component,createRef}from 'react'//通过解构赋值将createRef方法从‘react’中解构出来;若此处不解构 则后续代码不能直接使用createRef 需要React.createRef()书写形式调用
export default class Learn extends Componet{
h=createRef();
render(){
return(
<div>
<input ref={this.h}/>
</div>
)
}
componentDidMount(){
console.log(this.h)//{current:input}形式
console.log(this.h.current)
this.h.current.focus();//获得节点并调用相关方法
}
}
13. 记事本便签案例【考查受控组件及非受控组件】
效果描述:在input框内输入内容 按下键盘enter键 将数据自动添加到下方ul列表中
//index.js 入口文件
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(
<App />,
document.getElementById('root')
)
将组件抽离出来 形成简介的代码结构
//App.js 包含主要代码
import React, { Component } from "react";
import Input from "./Input";
import List from "./List";
class NewApp extends Component{
state = {
arr: JSON.parse(localStorage.getItem('todolist'))||[],
ischecked:''
}
onChecked=(index,e)=>{
this.setState({
ischecked:e.target.checked
})
}
keypress = (e) => {
if (e.keyCode === 13) {
let val=e.target.value
this.setState({
arr:[val,...this.state.arr]//不更改原来值 直接组成新的数组//展开运算符操作
},
() => {//setState是异步挂起 先执行后边代码 后执行setstate方法
//所以采用setstate第二个参数回调函数来调用本地存储实现同步存储
localStorage.setItem("todolist",JSON.stringify(this.state.arr))
})
e.target.value = ' '
}
}
delete = (index) => {
let newArr= this.state.arr.filter((val, idx) => index !== idx)
this.setState({
arr:newArr
})
localStorage.setItem("todolist",JSON.stringify(newArr))
}
render() {
const {arr}=this.state
console.log(this.state.arr)
return (
<div>
<Input keypress={this.keypress} />
<List data={arr} delete={this.delete}/>
</div>
)
}
}
export default NewApp
//Input.js
import React, { Component } from 'react'
export default class Input extends Component {
render() {
return (
<input onKeyDown={this.props.keypress}/>
)
}
}
//List.js
import React, { Component } from 'react'
const style = {
color: 'pink',
fontSize: '30px',
border: '1px #666 solid',
};
export default class List extends Component {
render() {
const { data }=this.props
return (
<>
<h2>正在进行的条数{data.length}</h2>
<ul >
{
data.map((item,index) =>(
<li style={style} key={item}>
{/* <input type='checkbox' checked={(e)=>this.state.isChecked(index)} onChange={this.onChecked} /> */}
{item}
<button onClick={()=>this.props.delete(index)}>删除</button>
</li>
)
)
}
</ul>
</>
)
}
}/*
//函数组件写法
export default List=(props)=> {
const { data ,delete}=this.props
return (
<>
<h2>正在进行的条数{data.length}</h2>
<ul >
{
data.map((item,index) =>(
<li style={style} key={item}>
{item}
<button onClick={()=>delete(index)}>删除</button>
</li>
)
)
}
</ul>
</>
)
}
*/
14. 关于React中img引入
- 方式1 import导入
import logo from './imgs/1.jpg'
const App=()=>{
return(
<img alt='' src={logo}>
)
}
- 方式2
const img=require('./imgs/1.jpg').default
const App=()=>{
return(
<img alt='' src={img}>
)
}
- 方式3
const App=()=>{
return(
<img alt='' src={require("./imgs/2.jpg").default}>
)
}
15. 关于React中行内样式引入
const style = {
color: 'pink',
fontSize: '30px',
border: '1px #666 solid',
};
render(){
<ul style={style}></ul>
}
<h2 style={{height:80 ,backgroundColor:"pink"}}>style</h2>
16. 行内引入html代码dangerouslySetInnerHTML
const App1 = () => {
const title='<h1>hello react</h1>'
return (
<div>
<div dangerouslySetInnerHTML={ {__html: title}}></div>
<div>{ title}</div>
</div>
)
}
17.Hook相关使用
类组件可以对事件设置状态,函数组件却无法直接进行setState设置状态,但是通常情况,类组件书写形式比函数组件复杂,考虑代码简洁性,则使用Hook实现函数组件的状态更新设置
- useState
//useState为变量创建状态及初始值
//参数:useState(状态,更新状态的方法) 返回值为一个数组
const [val,setVal]=useState('123')//关于数组解构 对命名无要求 仅依靠索引顺序就可以确认
return(
<div>
<input value={val} onChange={(e)=>{setVal(e.target.value)}} type='text'/>
<button>添加</button>
</div>
)
通常情况 设置多个setState对变量进行设置 若想实现设置一个setState完成对所有状态变量的设置 可以实现 但是具有弊端
//例如将学生信息作为整体 设置成一个setState 采用内置对象形式
const [stu,setStu]=useState({name:'张三',age:18})
return({
<div>name:{stu.name}--age:{stu.age}</div>
})
此操作具有弊端:无法单独更新一个属性 必须连带所有属性一齐更新 否则会丢失一个属性 即包含对多个属性值设定的setStu是将实参替换原状态
//错误写法
const upDataStu=()=>{
setStu({
name:val
})
}
//正确更新方法 即连带未修改的属性一起进行更新即可
const upDataStu=()=>{//且此处注意:严格模式下不允许直接函数名+箭头函数【需要加const进行标注】
setStu({
...stu,
name:val
})
}
向数组添加新的内容
function Hook() {
const [val, setVal] = useState('123')
const [list, setList] = useState([1, 2, 3, 4])
const upDataList = () => {
setList(
//此操作极慢 可以添加到数组中 但是只有在下次更新时才会加载出来
/* list.unshift(val)
console.log(list) */
[val,...list]
)
}
return (
<div>
<input /* value={val} */ onChange={(e) => { setVal(e.target.value) }} type='text' />
<button onClick={upDataList}>添加</button>
<ul>
{
list.map((item, index) => (
<li key={index}>{item }</li>
))
}
</ul>
</div>
)
}
- useEffect
引入:由于类似setInterval函数直接写在函数组件内会随着组件调用页面加载多次执行;若将此定时函数放在普通函数内置调用,则会内置后找不到外层变量。 所以此处引申出:useEffect方法执行此类有副作用的代码片段
关于useEffect方法
useEffect初始执行一次 后边看依赖项 若依赖项为空 则永远不会执行 不为空 则根据依赖项的变化情况进行指定更新
useEffect可以书写多次 多次调用 每个里边传特定内容 实现结构清晰
useEffect写在最外层 内置参数有两个1.执行代码 2.执行代码次数
- 参数为[]:
不传参数 任意一个状态更新 都会执行此代码 即依赖全部参数 默认执行一次
相当于类组件的componentDidMount 定时器、请求服务器等操作均写在useEffect函数内部
useEffect(()=>{
setInterval(()=>{
//巴拉巴拉
},1000)
},[])//与外置setInterval情况一致
- 参数为[num]:
传入依赖项 当num发生改变时才会执行这个函数 且变量可以获得最终实时更新后的数据
相当于componentDidUpdate
useEffect(()=>{
setInterval(()=>{
//巴拉巴拉
},1000)
},[num])
//可多依赖值
useEffect(()=>{
setInterval(()=>{
//巴拉巴拉
},1000)
},[num,list])
- 参数任意
实现卸载状态 相当于componentWillunmount
useEffect(()=>{
let timer=setInterval(()=>{
//巴拉巴拉
},1000)
return()=>{
clearInterval(timer)
}
},[])
实现简单计数器功能
function Hook() {
const [num, setNum] = useState(0)
useEffect(() => {
let timer = setInterval(() => {
setNum(
(num)=>num+1
)
},1000)
return () => {
clearInterval(timer)
}
})
return (
<div>
<h2>{num}</h2>
</div>
)
}
- useRef 非受控
类比类组件的createRef
const Input = () => {
let inpRef = useRef()
useEffect(() => {
inpRef.current.value='100'
}, [])
return (
<div>
<input ref={inpRef} />
</div>
)
}
export default Input
18. 关于memo、useCallback等优化操作
- memo
实现页面优化 访问操作流畅 属性不进行改变则不会进行二次更新 提高效率
通常包裹一个函数 放在外层 没有附加参数
//会进行属性props的比较 相同则不会重复执行此代码
const Header=memo(()=>{
console.log('header')
return <h1>Todolist</h1>
})
- useCallback
缓存一个函数
const addItem=useCallback(
(e)=>{
if(e.keyCode===13){
setList((a)=>[e.target.value,...a])
}
}
,[])
setList参数可为一个箭头函数 获取到当前变量的最新状态;若该值不发生变化 则可以直接印 不需要用箭头函数
- useMemo
缓存一个值 通常在函数组件内部 用于存储某个特定值的形式出现
const val=useMemo(()=>{
//巴拉巴拉
return 100
},[])
- 完整案例
//Hook.js
import React ,{useState,memo,useCallback}from'react'
import Input from './Input'
import List from './List'
const Header=memo(()=>{
console.log('header')
return <h1>TodoList</h1>
})
const Hook=()=>{
const [list,setList]=useStata([])
const addItem=useCallback(
(e)=>{
if(e.keyCode===13){
setList((a)=>[e.target.value,...a])
}
}
,[])
return(
<div>
<Header/>
<Input addItem={addItem}/>
<List list={list}/>
</div>
)
}
export default Hook
//Input.js
import React,{memo} from'react'
const Input=memo(
({addItem})=>{
return(
<div>
<input onKeyDown={addItem} type='text'/>
</div>
)
}
)
export default Input
//List.js
import React,{memo}from'react'
const List=memo(()=>{
console.log('re-list')
return(
<div>
<ul>
list.map((item,index)=>(
<li key={index}>
{item}
</li>
))
</ul>
</div>
)
}
)
19. React路由
npm yarn的安装操作[两者操作实现效果一致 只是yarn会更加便利快速]
npm start & yarn start
npm install packageName --save & yarn add package
npm uninstall packageName & yarn remove packageName
全局路由配置
- npm install react-router-dom 安装指定路由的包
- import {BrowerRouter,Route,Link} from 'react-route-dom’页面引入指定需要的包
同一组件实现不同路由跳转
import React from 'react'
import {BrowserRouter,Route,Link}from 'react-router-dom'
const App=()=>{
return(
<BrowserRouter>
<Link to='/home'>首页</Link>
<Link to='/new'>新手入门</Link>
<Link to='/login'>登录</Link>
<Switch>
<Route exact path='/'component={<Strict/>}>//注解1
<Route path='/home'><Home/></Route>//注解2
<Route path='/new' component={<New/>}/>
<Route path='/login' component={<Login/>}/>
<Route path='/:username' component={<User/>} />//注解3
<Redirect to='/home'/>//默认跳到指页面
</Switch>
</BrowerRouter>
)
}
export default App
注解1:由于所有路径都可以匹配单一/路径 所以会一直在/路径中执行死循环 改变此状:加exact限制其跳转-即只有在单一[/]情况下才会跳转
注解2:关于Route的单双标签形式:单标签可以实现路劲传参 通过props获得其属性等
注解3:外层没有Switch标签 路由/:userName会被匹配到其他所有的路由 除非将该路由放在所有路由的最后边 才会防止其被误执行;添加Switch进行包裹,能够保证所有路由均被单独各自执行/:userName路由也可以不被其他路由误执行
- 关于Link组件的其他写法
<Link to='/home'>首页</Link>
//此写法无法对组件的css样式进行设定 特引出NavLink组件
<NavLink activeStyle={{color:'red',borderBottom:'4px solid blue'}} to='/home'>首页</NavLink>
<NavLink activeStyle={{color:'red'}} to={{pathname:'/home',state={id:100}}}>传参路由</NavLink>
- 二级路由写法
<div>
<div>
<BrowerRouter>
<Link to='/home/all'>全部</Link>
<Link to='/home/share'>分享</Link>
<Link to='/home/good'>精华</Link>
<Switch>
<Route path='/home/all' render={()=><h1>全部</h1>}/></Route>
<Route path='/home/share' render={()=><h1>分享</h1>}/></Route>
<Route path='/home/good' render={()=><h1>精华</h1>}/></Route>
<Redirect to='/home/all'>
</Switch>
</BrowerRouetr>
</div>
</div>
useHistory
history.push相当于Link to的作用 然后寻找指定路由 进行跳转
import {useHistory}from 'react-router-dom'
const Home=()=>{
const history=useHistory()
return(
data.map(item=>(
<li onClick={()=>history.push('/detail/'+item.id)}>
</li>
))
)
}
useParams
返回URL参数的键/值的对象
<Route path='/detail:id'><Detail/></Route>
//Detail.js
import {useParams}from 'react-router-dom'
const Detail=()=>{
const {id}=useParams()//返回URL参数的键/值对的对象 即传来的数据
}
useRouteMatch
20. 关于获取属性判空的两种写法
return(
<div>
<div>登录名: {topic.author&&topic.author.loginname}</div>
<img src={topic.author?.avatar_url} alt=''/>
<div dangerouslySetInnerHTML={{__html:topic.content}}></div>
<div>
)
21. 权限路由
//App.js
import React from'./react'
import Header from'./Header'
import Content from './Content'
import {BrowserRouter}from 'react-router-dom'
const App=()=>{
return(
<BrowserRouter>
<Header/>
<Content/>
</BrowserRouter>
)
}
export default App
//Header.js
import React from'react'
import './Header.css'
import {NavLink} from 'react-router-dom'
const Header=()=>{
return(
<div className='nav'>
<NavLink exact activeStyle={{color:'red'}} to='/home'>首页</NavLink>
<NavLink activeStyle={{color:blue}} to='/new'>新手入门</NavLink>
<NavLink to='/collect'>收藏<NavLink>
<NavLink to='/login'>登录</NavLink>
</div>
)
}
export default Header
//Content.js
import React from 'react'
import {Route,Switch}from'react-router-dom'
import Home from'./Home'
import Detail from './Detail'
const New=()=>{
return(
<h1>新手入门</h1>
)
}
const Login=()=>{
return(
<h1>登录</h1>
)
}
const PrivateRoute=({component:Com,...rest})=>{//通过解构将路由传来的数据进行重命名及解构
return(<Route {...rest} render={(props)=>{
if(!localStorage.getItem('isLogin')){
props.history.push('/login')//当当前存储中没有登录信息时 由收藏页面跳转到登录页面要求用户进行登录操作
}else{
return<Com/>//当当前存储中有登录信息 则按照传来的component数据将正常跳转的页面进行显示返回
}
}
}/>
)
}
const Content=()=>{
return(
<div className='route-left'>
<Switch>
<Route exact path='/home' component={Home}/>
<Route path='/new' component={New}/>
<Route path='/login' component={Login}/>
<Route path='/detail/:id' component={Detail}/>
<PrivateRoute path='/collect' component={Collect}/>
<Route render={()=><h1>您访问的页面不存在</h1>}/>
</Switch>
</div>
<div className='route-right'>
</div>
)
}
export default Content
//Home.js
import React,{useState,useEffect} from 'react'
import {Link,useHistory,Route,Redirect,useRouteMatch,Switch}from 'react-router-dom'
const Home=()=>{
const history=useHistory();
const [path,url]=useRouteMatch();
const [topics,setTopics]=useState([]);
useEffect(()=>{
fetch("https://cnodejs.org/api/v1/topics")
.then(res=>res.json())
.then(res=>{
res&&setTopics(res.data)//当能够获取到返回值res的时候 就在重新设定topics的值
})
.catch((err)=>{
console.log(err)
})
},[])
return(
<ul>
<div style={{height:300}}>
<Link to={`${url}/all`}>全部</Link>
<Link to={`${url}/share`}>分享</Link>
<Link to={`${url}/good`}>精华</Link>
<Switch>
<Route path={`${path}/all`} render={()=><h1>全部</h1>}/>
<Route path={`${path}/share`} render={()=><h1>分享</h1>}/>
<Route path={`${path}/good`} render={()=><h1>精华</h1>}/>
<Redirect to='home/all'/>
</Switch>
</div>
{
topics.map(item=>(
<li
key={item.id}
onClick={()=>history.push('/detail/'+item.id)}
>
{item.title}
</li>
))
}
</ul>
)
}
export default Home
//Deatil.js
import React,{useState,useEffect} from 'react'
import {useParams,useLocation,useRouteMatch} from 'react-router-dom';
const Detail=()=>{
const {id}=useParams()//获取并解构从路径传来的id值
const[topic,setTopic]=useState({})
useEffect(()=>{
fetch('https://cnodejs.org/api/v1/topic/',+id)
}
.then(res=>res.json())
.then(res=>{
res&&res.data
})
.catch((err)=>{console.log(err)})
,[])
return(
<div>
<div>登录名:{topic.author?.loginname}</div>
<img src={topic.author?.avatar_url} alt=""/>
<div dangerouslySetInnerHTML={{__html:topic.content}}></div>
</div>
)
}
22. 关于自定义hook组件
//App.js
import React from 'react';
import Header from './Header';
import { BrowserRouter, Route, Link,Switch } from 'react-router-dom';
import Content from './Content';
import AuthProvider from './AuthProvider'
const App=()=>{
retrun(
<AuthProvider>
<BrowsweRouter>
<Header/>
<Content/>
</BrowserRouter>
</AuthProvider>
)
}
export default App
//AuthProvider.js
import {createContext,useState,useContext} from 'react'
const AuthContext=createContext();
export const useAuth=()=>{
return useContext(AuthContext)
}
const AuthProvider=(props)=>{
const [isLogin,setIsLogin]=useState(localStorage.getItem('isLogin')||false)
return <AuthContext.Provider value={{isLogin,setIsLogin}}>
{props.children}//此处必须引入 才能获得到所显示的内容
</AuthContext.Provider>
}
export default AuthProvider
//Header.js
import React from 'react';
import './Header.css';
import {Link,NavLink,useHistory} from 'react-router-dom';
import {useAuth} from './AuthProvider'
const Header=()=>{
const history=useHistory();
const {isLogin,setIsLogin}=useAuth();
const logout=()=>{
setIsLogin(false)
localStorage.removeItem('isLogin')
history.replace('/login')
}
return(
<div className='header-wrap'>
<div className='header'>
<div className='nav'>
<NavLink exact activeStyle={{color: 'red'}} to='/'>首页</NavLink>
<NavLink activeStyle={{color: 'red'}} to='/new'>新手入门</NavLink>
<NavLink activeStyle={{color: 'red'}} to='/collect'>收藏</NavLink>
<NavLink activeStyle={{color: 'red'}} to='/user'>个人中心</NavLink>
{
isLogin?
<>
<a>设置</a>
<a onClick={logout}>退出</a>
:
<NavLink to='/login'>
登录
</NavLink>
</>
}
</div>
</div>
</div>
)
}
//Content.js
import React from 'react'
import { Route, Switch, Redirect, useHistory, useLocation } from 'react-router-dom';
import './Content.css';
import Home from './Home';
import Detail from './Detail';
import {useAuth} from './AuthProvider';
const PrivateRoute=({component:Com,children,...rest})=>{
return <Route {...rest} render={(props)=>{
if(!localStorage.getItem('isLogin')){
return <Redirect to={{
pathname:'/login',
state:props.location
}}/>
}else{
return Com? <Com/>:children;
}
}
}/>
}
const Content=()=>{
return(
<Switch>
<Route path='/home' component={Home}/>
<Route path='/new' component={New}/>
<Route path='/login'component={Login}/>
<Route path='/detail/:id' cmponent={Deatil}/>
<PrivateRoute path='/collect'component={Collect}/>
<Route render={()=><h1>你访问的页面不存在</h1>}/>
</Switch>
)
}
export default Content
//hook.js
import {useState,useEffect} from 'react'
export const useRequest=(url)=>{
const [data,setData]=useState([])
const [options,setOptions]=useState({tab:'all'})
useEffect(()=>{
fetch(url+'?tab='+options.tab)
.then(res=>res.json())
.then(res=>{
res&&setData(res.data)
})
.catch((err)=>{
console.log(err)
})
},[options])
return[data,setOptions]
}
//Home.js
import React from 'react'
import {Link,useHistory,Route,Redirect,useRouteMatch} from 'react-router-dom';
import {useRequest} from './hooks'
const Home = () => {
const [topics,setOptions] = useRequest('https://cnodejs.org/api/v1/topics');
const history = useHistory();
const {path,url} = useRouteMatch();
return (
<ul>
<div style={{height: 100}}>
<Link to={`${url}/all`}>全部</Link>
<Link to={`${url}/share`}>分享</Link>
<Link to={`${url}/good`}>精华</Link>
<div>
<Route path={`${path}/all`} render={()=><h1>全部</h1>}/>
<Route path={`${path}/share`} render={()=><h1>分享</h1>} />
<Route path={`${path}/good`} render={()=><h1>精华</h1>} />
<Redirect to={`/home/all`} />
</div>
</div>
<div>
<button>全部</button>
<button onClick={()=>setOptions({tab:'good'})}>精华</button>
<button>分享</button>
</div>
{
topics.map(item=>(
<li
key={item.id}
onClick={()=>history.push('/detail/'+item.id)}
>
{item.title}
</li>
))
}
</ul>
)
}
export default Home;
//Detail.js
import React from 'react'
import {useParams} from 'react-router-dom';
import {useRequest} from './hooks';
const Detail=()=>{
const{id}=useParams();
const topic=useRequest('https://cnodejs.org/api/v1/topic/'+id)
return(
<div>
<div>
登录名:{topic.author?.loginname}
<img src={topic.author?.avatar_url} alt="" />
</div>
<div dangerouslySetInnerHTML={{__html:topic.content}}>
</div>
</div>
)
}
23.redux组件
//App.js
import Header from "./Header";
import store from "./store";
import { BrowserRouter} from 'react-router-dom';
import Content from "./Content";
import {Provider} from 'react-redux'
const App=()=>{
return(
<Provider store={store}>
<BrowserRouter>
<Header/>
<Content/>
</BrowserRouter>
</Provider>
)
}
export default App
//store.js
import {createStore}from'redux'
const initState={
isLogin:false,
a:100,
userinfo:{}
}
const loginReducer=(state=initState,action)=>{
switch(action.type){
case 'loginSuccess':
return{
...state,
isLogin:action.isLogin
};
case 'getUserInfoSuccess':
return{
...state,
userinfo:action.payload
}
default:
return state;
}
}
const store=createStore(loginReducer)
export default store
//Header.js
import React from 'react'
import './Header.css';
import {NavLink,useHistory}from 'react-router-dom'
import {useSelector}from 'react-redux'
import {useDispatch} from 'react-redux'
const Header=()=>{
const dispatch=useDispatch()
const history=useHistory()
const Login=userSelector(state=>state)
const logout=()=>{
dispatch({type:'loginSuccess',isLogin:false})
localStorage.removeItem('isLogin')
history.replace('/login')
}
return(
<div className='header-wrap'>
<div className='header'>
<img src='http://static.nodejs.cn/_static/img/logo.svg' alt='' className='logo' />
<div className='nav'>
<NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to='/home'>首页</NavLink>
<NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to='/new'>新手入门</NavLink>
<NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to='/collect'>收藏</NavLink>
<NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to='/user'>个人中心</NavLink>
{/* <NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to='/zhangsan'>API</NavLink>
<NavLink activeStyle={{color:'green', borderBottom:'4px solid blue'}} to={{pathname:'/list',state:{id:100}}}>传参路由</NavLink>
*/}
{
isLogin ? <><a href='###'>设置</a><a href='###'onClick={logout}>退出</a></>
:<NavLink activeStyle={{color: 'green', borderBottom: '4px solid blue' }} to='/login'>登录</NavLink>
}
</div>
</div>
</div>
)
}
//Content.js
import React from 'react'
import { Route, Switch,Redirect } from 'react-router-dom';
import './Content.css';
import Home from './Home';
import Detail from './Detail';
import Login from './Login';
const Collect = () => <h1>收藏页面</h1>
const New = (props) => {
return <>
{props.children}
<h1>新手入门</h1>
</>
}
const PrivateRoute=({component:Com,children,...rest})=>{
return<Route {...rest} render={(props)=>{
if(!localStorage.getItem('isLogin')){
return <Redirect to={{
pathname:'/login',
state:props.location
}}/>
}else{
return Com?<Com/>:children
}
}
}/>
}
const Content=()=>{
return(
<Switch>
<Route path='/home' component={Home}/>
<Route path='/login'component={Login}/>
<Route path='/detail/:id' component={Detail}/>
<PrivateRoute path='/new' component={New} />
<PrivateRoute path='/collect' component={Collect} />
<Route render={()=><h1>您访问的页面不存在</h1>}/>
</Switch>
)
}
export default Content
//hook.js
import { useState, useEffect } from "react"
export const useRequest = (url) => {
const [data, setData] = useState([])
const [options,setOptions]=useState({tab:'all',page:1})
useEffect(()=>{
fetch(`${url}?tab=${options.tab}&pages=${options.page}`)
.then(res => res.json())
.then(res => {
res&&setData(
res.data
)
})
.catch((err)=>{console.log(err)})
},[options])
return [data,setOptions,options];
}
//Home.js
import React from 'react'
import {Link,useHistory,Route,Redirect,useRouteMatch} from 'react-router-dom';
import {useRequest} from './hook'
const Home = () => {
const [topics,setOptions,options] = useRequest('https://cnodejs.org/api/v1/topics');
const history = useHistory();
const {path,url} = useRouteMatch();
const tabs = [
{title:'全部',tab:'all'},
{title:'精华',tab:'good'},
{title:'分享',tab:'share'},
{title:'问答',tab:'ask'},
{title:'招聘',tab:'job'},
]
const pageButton = [1,2,3,4,5,6,7,8,9];
return (
<ul>
<div style={{height: 100}}>
<Link to={`${url}/all`}>全部</Link>
<Link to={`${url}/share`}>分享</Link>
<Link to={`${url}/good`}>精华</Link>
<div>
<Route path={`${path}/all`} render={()=><h1>全部</h1>}/>
<Route path={`${path}/share`} render={()=><h1>分享</h1>} />
<Route path={`${path}/good`} render={()=><h1>精华</h1>} />
<Redirect to={`/home/all`} />
</div>
</div>
<div>
{
pageButton.map(item=>(
<button
key={item}
onClick={()=>setOptions({...options,page:item})}
>
{item}
</button>
))
}
</div>
<div>
{
tabs.map(item=>(
<button
key={item.tab}
onClick={()=>setOptions({tab:item.tab,page:1})}
>
{item.title}
</button>
))
}
</div>
{
topics.map(item=>(
<li
key={item.id}
onClick={()=>history.push('/detail/'+item.id)}
>
{item.title}
</li>
))
}
</ul>
)
}
export default Home;
//Detail.js
import React from 'react'
import{useParams}from'react-router-dom'
import { useRequest } from './hook'
function Detail() {
const { id } = useParams()
const [topic]= useRequest("https://cnodejs.org/api/v1/topic/"+id)
return (
<div>
<div>登录名:{topic.author&&topic.author.loginname}</div>
<img src={topic.author?.avatar_url}alt=''style={{width:'50px'}}/>
<div dangerouslySetInnerHTML={{__html:topic.content}}></div>
</div>
)
}
export default Detail
//Login.js
import React from 'react'
import { useHistory,useLocation } from 'react-router'
import {useDispatch} from 'react-redux'
function Login() {
const dispatch=useDispatch()
const history = useHistory()
const location = useLocation()
const login = () => {
dispatch({type:'loginSuccess',isLogin:true})
localStorage.setItem('isLogin',true)
history.replace(location.state||'/')//判断 当直接在登录页点击登录时不报错 直接跳转到首页
}
return (
<div>
<h1>登录页面</h1>
<button onClick={login}>登录</button>
</div>
)
}
export default Login