React学习笔记

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值