React - Redux Hooks的使用细节详解

Posted 学全栈的灌汤包

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React - Redux Hooks的使用细节详解相关的知识,希望对你有一定的参考价值。

文章目录

Redux Hooks

Redux中Hooks介绍

在之前的redux开发中,为了让组件和redux结合起来,我们使用了react-redux库中的connect:

但是这种方式必须使用高阶函数结合返回的高阶组件;

并且必须编写:mapStateToProps和 mapDispatchToProps映射的函数, 具体使用方式在前面文章有讲解;

在Redux7.1开始,提供了Hook的方式,在函数组件中再也不需要编写connect以及对应的映射函数了

useSelector的作用是将state映射到组件中:

参数一: 要求传入一个回调函数, 会将state传递到该回调函数中; 回调函数的返回值要求是一个对象, 在对象编写要使用的数据, 我们可以直接对这个返回的对象进行解构, 拿到我们要使用state中的数据

const  counter  = useSelector((state) => 
  return 
    counter: state.counter.counter
  
)

参数二: 可以进行比较来决定是否组件重新渲染;

useSelector默认会比较我们返回的两个对象是否相等;

如何可以比较呢?

  • 在useSelector的第二个参数中, 传入react-redux库中的shallowEqual函数就可以进行比较
import  shallowEqual  from 'react-redux'

const  counter  = useSelector((state) => (
  counter: state.counter.counter
), shallowEqual)

也就是我们必须返回两个完全相等的对象才可以不引起重新渲染;

useDispatch非常简单,就是调用useDispatch这个Hook, 就可以直接获取到dispatch函数,之后在组件中直接使用即可;

const dispatch = useDispatch()

我们还可以通过useStore来获取当前的store对象(了解即可, 不建议直接操作store对象);


Redux中Hooks使用

我们来使用Redux的Hooks在App组件实现一个计数器, 在App的子组件中实现一个修改message的案例:

首先我们先创建一个简单的store

// store/modules/counter.js

import  createSlice  from "@reduxjs/toolkit";

const counterSlice = createSlice(
  name: "counter",
  initialState: 
    counter: 10,
    message: "Hello World"
  ,
  reducers: 
    changeNumberAction(state,  payload ) 
      state.counter = state.counter + payload
    ,
    changeMessageAction(state,  payload ) 
      state.message = payload
    
  
)

export const  changeNumberAction, changeMessageAction  = counterSlice.actions

export default counterSlice.reducer
// store/index.js

import  configureStore  from "@reduxjs/toolkit";
import counterSlice from "./modules/counter"

const store = configureStore(
  reducer: 
    counter: counterSlice
  
)

export default store

要使用react-redux库需要导入Provider对App组件进行包裹

import React from "react"
import ReactDOM from "react-dom/client"
import  Provider  from "react-redux"
import App from "./12_Redux中的Hooks/App"
import store from "./12_Redux中的Hooks/store"

const root = ReactDOM.createRoot(document.querySelector("#root"))

root.render(
  <Provider store=store>
    <App/>
  </Provider>
)

在组件时使用useSelector和useDispatch实现获取store中的数据和修改store中数据的操作

import React,  memo  from 'react'
import  useDispatch, useSelector  from 'react-redux'
import  changeMessageAction, changeNumberAction  from './store/modules/counter'

// 子组件Home
const Home = memo(() => 
  console.log("Home组件重新渲染")
  
  // 通过useSelector获取到store中的数据
  const  message  = useSelector((state) => (
    message: state.counter.message
  ))

  // useDispatch获取到dispatch函数
  const dispatch = useDispatch()
  function changeMessage() 
    dispatch(changeMessageAction("Hello ChenYq"))
  

  return (
    <div>
      <h2>message</h2>
      <button onClick=changeMessage>修改message</button>
    </div>
  )
)


// 根组件App
const App = memo(() => 
  console.log("App组件重新渲染")
  
  // 通过useSelector获取到store中的数据
  const  counter  = useSelector((state) => (
    counter: state.counter.counter
  ))

  // useDispatch获取到dispatch函数
  const dispatch = useDispatch()
  function changeNumber(num) 
    dispatch(changeNumberAction(num))
  
  
  return (
    <div>
      <h2>当前计数: counter</h2>
      <button onClick=() => changeNumber(1)>+1</button>
      <button onClick=() => changeNumber(-1)>-1</button>

      <Home/>
    </div>
  )
)

export default App

现在我们已经在组件中使用并且修改了了store中的数据, 但是现在还有一个小问题(性能优化)

当App组件中修改了counter时, App组件会重新渲染这个是没问题的; 但是Home组件中使用的是message, 并没有使用counter, 却也会重新渲染; 同样的在Home子组件中修改了message, 根组件App也会重新渲染; 这是因为在默认情况下useSelector是监听的整个state, 当state发生改变就会导致组件重新渲染

要解决这个问题就需要使用useSelector的第二个参数来控制是否需要重新渲染, 我们只需要在useSelector函数中传入react-redux库中的shallowEqual函数即可, 它内部会自动进行一个浅层比较, 当使用的state中的数据确实发生变化的时候才会重新渲染

import React,  memo  from 'react'
import  useDispatch, useSelector, shallowEqual  from 'react-redux'
import  changeMessageAction, changeNumberAction  from './store/modules/counter'

// 子组件Home
const Home = memo(() => 
  console.log("Home组件重新渲染")

  const  message  = useSelector((state) => (
    message: state.counter.message
  ), shallowEqual)

  const dispatch = useDispatch()
  function changeMessage() 
    dispatch(changeMessageAction("Hello ChenYq"))
  

  return (
    <div>
      <h2>message</h2>
      <button onClick=changeMessage>修改message</button>
    </div>
  )
)


// 根组件App
const App = memo(() => 
  console.log("App组件重新渲染")

  // 通过useSelector获取到store中的数据
  const  counter  = useSelector((state) => (
    counter: state.counter.counter
  ), shallowEqual)

  // useDispatch获取到dispatch函数
  const dispatch = useDispatch()
  function changeNumber(num) 
    dispatch(changeNumberAction(num))
  
  
  return (
    <div>
      <h2>当前计数: counter</h2>
      <button onClick=() => changeNumber(1)>+1</button>
      <button onClick=() => changeNumber(-1)>-1</button>

      <Home/>
    </div>
  )
)

export default App

react+redux+hooks

2019年5月24日 开始对于react的学习

http://huziketang.mangojuice.top/books/react/lesson46

开始提要:reactjs是不依赖nodejs的,但是webpack是依赖的,所以我们react开发也要安装node环境!
安装node的教程我要另开一个笔记,就在自己的博客园里面了:

 

react和小程序以及vue都是不一样的,他完全改变了我对隔离霜的看法【口区】

废话不啰嗦,现在开始!

首先,惯例介绍我的参考文献:https://react.docschina.org/docs/introducing-jsx.html

1、hello.world JSX
react的语法不同,像是xml语法,又像是js语法,本身就是js语法的一种扩展,所以他用jsx语法
JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析
我们想要创建一个react hello实例的话,就像01demo.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
ReactDOM.render(
<h1>Hello World!</h1>,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这里要注意,我们的js要引入完,因为不是直接用cnpm引入的,所以三个都要引入
以及,他的script,type不再是:text/javascript 而是text/babel 不然也是不对的

用react创建新dom的时候,属性要用驼峰式,比如class变成className

2、元素渲染
元素描述了你在屏幕上想看到的内容
const element = <h1>hello world!</h1>
比如上面这个,hello world就是他想渲染的

我们在写01demo的时候,注意到,div里面有一个id=root的,这就是根节点,因为该节点内的所有内容都将由react dom管理
仅仅使用react构建的应用通常只有单一的根DOM节点,当然也可以有多个独立的,不过这里先不说了

想要将一个react元素渲染到根root节点中,只需把它们一起传入reactDOM.render():
const element = <h1>hello world!</h1>
ReactDOM.render(element,document.getElementById(‘root‘));

我们这里再来一个02demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
#root{
color: #f66;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
const element = <h1>张卫健,你好啊~</h1>
ReactDOM.render(
element,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这个例子说明了我们的猜想,并且!说明了css是可以直接写哒~

基本上react元素是不可变对象,一旦被创建,就无法再次更改他的子元素或者属性。所以我们想要更新这个元素的话,就必须重新创建一个新的元素,并且将其传入reactDOM。render()
而且这也不是一劳永逸的,比如只想换里面的一部分元素,就没办法了,先看03demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function tick (){
const element = (
<div>
<h1>这里来个定时器</h1>
<h2>TIME: {new Date().toLocaleTimeString()}.</h2>
</div>
)
ReactDOM.render(
element,
document.getElementById(‘root‘)
)
}
setInterval(tick,1000);
</script>
</body>
</html>
就想到于,每一秒重新更新一次root里面的内容 但是react有一个优点,就是只更新他比较出来不一样的那一部分!
换句话说,react dom元素会和他的子元素之间的状态进行比较,进行必要的更换,这点可以通过浏览器的检查工具看到:
<div id="root">
<div>
<h1>这里来个定时器</h1>
<h2>TIME: 下午4:42:49.</h2> //只有这里一直闪,背景为紫色,说明只有这里被重新渲染了
</div>
</div>

3、组件&props
组件允许你将UI拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件,从概念上类似于JavaScript函数。他接受任意的入参(就是props),并返回用于描述页面展示内容的react元素

3.1、函数组件与class组件
3.1.1、定义组件最简单的方式就是编写JavaScript函数:
function Welcome(prop){
return <h1>hello , {props.name}</h1>
}

3.1.2、用es6的class定义组件
class welcome extends React.Component{
render(){
return <h1>hello , {this.props.name}</h1>
}
}

用class定义组件,涉及到props定义的问题,所以在学习class的其他属性前,我们用函数定义组件。

3.2、渲染组件
我们之前一直用dom标签,其实react元素也可以是自定义组件,我们看04demo

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function Welcome (props){
return <h1>hello,{props.name}</h1>

}
const element = <Welcome name="张卫健" />;
ReactDOM.render(
element,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这里要记得,函数名一定要是【大写】的!必须是!!!
并且,element不单单可以等于一个元素,也可以等于多个,比如05demo,这里就不复制了,主要是为了下面组件组合:

3.3、组合组件
组件可以再引入其他组件,我们用函数组件的方法,来个06demo

3.4、提取组件
就是把组件拆分成更小的组件
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}

首先上面就是一个组件,但是她太繁琐了,不好维护,我们就要拆分,下面看07demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function formatDate(date) {
return date.toLocaleDateString();
}
//先是avatar
function Avatar(props){
return (
<img className="Avatar"
src={props.user.avatarUrl}
alt={props.user.name}
/>
)
}

//然后userInfo
function UserInfo(props){
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">
{props.user.name}
</div>
</div>
)
}

function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}

const comment = {
date: new Date(),
text: ‘I love dkzhang so much!‘,
author: {
name: ‘Hello dk‘,
avatarUrl: ‘https://placekitten.com/g/64/64‘,
},
};

ReactDOM.render(
<Comment date={comment.date} text={comment.text} author={comment.author}/>,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这就完成了一个组件的拆分

并且,我们要知道,props只有可读性,也就是说,他不会更改入参。但是学习了state之后,就可以更改,这就是后话了~

今天的学习到此为止,明天就是周六了,下周继续呀!!!我总觉着下周过来会忘完怎么回事??? ----0524END

4、state&生命周期
我们在03demo写定时器的时候,发现这样一直全部更新状态比较鸡肋,这一节我们学习如何封装真正可复用的clock组件。
首先我们先把定时器的外观封装:
function Clock(props){
return (
<div>
<h1>hello dk!</h1>
<h2>It is {props.date.toLocaleTimeString().}</h2>
</div>
)
}
function tick(){
ReactDOM.render(
<Clock date = {new Date()} />,
document.getElementById(‘root‘)
);
}
setInterval(tick,1000);

但是我们又不希望一直写<Clock date = {new Date()} />,这个时候,就要用到state了
state与props相似,但是state是私有的,并且完全受控于当前组件。 而且state只能用在类组件里面,不能用于函数组件,所以要改写函数组件为【类组件】

所以我们最后的修改如08demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class Clock extends React.Component{
constructor(props){
super(props);
this.state = {
date:new Date()
};
}
componentDidMount(){
this.timeID = setInterval(
()=>this.tick(),
1000
);
}
componentWillUnmount(){
clearInterval(this.timeID);
}
tick (){
this.setState({
date:new Date()
});
}
render(){
return(
<div>
<h1>这里来个定时器</h1>
<h2>TIME: {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
ReactDOM.render(
<Clock />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
要注意上面的两个周期函数,她代表了一个是【挂载】一个是【卸载】,当我们的组件已经被渲染到DOM中后,会运行componentDidMount方法,
当我们的组件从DOM移除的时候,就会调用componentWillUnmount方法

5、事件处理
react事件的命名采用小驼峰式,而不是纯小写。
使用jsx语法时你需要传入一个函数作为事件处理函数,也就是要用大括号包起来,如下:
<button onClick={activateLasers}>
Activate Lasers
</button>

React中另一个不同点是你不能通过返回false的方式阻止默认行为,你必须显式的使用preventDefault。如:
function ActionLink(){
function handleClick(e){
e.preventDefault();
console.log(‘The link was clicked.‘);
}

return(
<a herf="#" onClick={handleClick}>
Click me
</a>
);
}

在这里,e是一个合成事件。

使用React时,你一般不需要使用addEventListener为已创建的DOM元素添加监听器。React恰恰与之相反,你仅需要在该元素初始渲染的时候添加一个监听器。
当你使用类语法定义一个组件的时候,通常的做法是将事件处理函数声明为class中的方法。如09demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class Toggle extends React.Component{
constructor(props){
super(props);
this.state = {
isToggleOn:true
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
this.setState(state => ({
isToggleOn:!state.isToggleOn
}));
}
render(){
return(
<button onClick={this.handleClick}>
{this.state.isToggleOn?‘ON‘:‘OFF‘}
</button>
)
}
}
ReactDOM.render(
<Toggle />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

6、条件渲染
可以用if或者条件运算符去创建元素来表现当前的状态,然后让React根据他们来更新UI。
如10demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function UserGreeting(props){
return <h1>Welcome back!</h1>
}
function GuestGreeting(props){
return <h1>Please sign up!</h1>
}
function Greeting(props){
const isLoggedIn = props.isLoggedIn;
if(isLoggedIn){
return <UserGreeting />;
}else{
return <GuestGreeting />;
}
}
ReactDOM.render(
<Greeting isLoggedIn = {false} />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

再来一个比较高级的11demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function UserGreeting(props){
return <h1>Welcome back!</h1>
}
function GuestGreeting(props){
return <h1>Please sign up!</h1>
}
function Greeting(props){
const isLoggedIn = props.isLoggedIn;
if(isLoggedIn){
return <UserGreeting />;
}else{
return <GuestGreeting />;
}
}

function LoginButton(props){
return(
<button onClick={props.onClick}>
logIn
</button>
)
}
function LogoutButton(props){
return(
<button onClick={props.onClick}>
logOut
</button>
)
}

class LoginControl extends React.Component{
constructor(props){
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {
isLoggedIn:false
}
}
handleLoginClick(){
this.setState({
isLoggedIn:true
})
}
handleLogoutClick(){
this.setState({
isLoggedIn:false
})
}
render(){
const isLoggedIn = this.state.isLoggedIn;
let button;
if(isLoggedIn){
button = <LogoutButton onClick={this.handleLogoutClick} />
}else{
button = <LoginButton onClick={this.handleLoginClick} />
}
return(
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
)
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

6.1.与运算符&&
通过花括号包裹代码,可以在jsx中嵌入任何表达式。因为在JavaScript中,true&&expression总是会返回expression,false&&expression总是会返回false,所以我们可以这样写12demo:
function Milbox(props){
const unreadMessages = props.unreadMessages;
return(
<div>
<h1>Hello DK !</h1>
{unreadMessages.length>0&&
<h2>
You have {unreadMessages.length} unread messages.
</h2>}
</div>
)
}
const messages = [‘React‘,‘liulu‘,"dkzhang"];
ReactDOM.render(
<Milbox unreadMessages={messages}/>,
document.getElementById(‘root‘)
)

6.2.三目运算符
这个比较简单,直接举一个例子
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? ‘currently‘ : ‘not‘}</b> logged in.
</div>
);
}

6.3.阻止组件渲染
在极少数情况下,我们可能会希望组件隐藏,即使他已经被其他组件渲染,如果要完成这样的操作,我们可以让render方法直接返回null,而不进行任何渲染,如13demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function Warning(props){
if(!props.warn){
return null;
}
return(
<div className="warning">
Warning!
</div>
)
}
class Page extends React.Component{
constructor(props){
super(props);
this.state = {
showWarning:true
};
this.handleToggleClick = this.handleToggleClick.bind(this);
}

handleToggleClick(){
this.setState(state=>({
showWarning:!state.showWarning
}));
}
render(){
return(
<div>
<Warning warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? ‘Hide‘:‘Show‘}
</button>
</div>
)
}
}
ReactDOM.render(
<Page />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
这里的重点在于if(!props.warn){return null;},如果我们进入这里的话,后面的就不会再渲染了。否则 ,执行下面的。

7、列表&key
这个没什么好说的,通过14demo直接看效果:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
const array = [1,2,3,4,5];
const newArray = array.map((num)=>
<li>{num}</li>
)
ReactDOM.render(
<ul>{newArray}</ul>,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
我们在15demo中,将这个demo变成一个组件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function SetArray(props){
const numbers = props.numbers;
const setItems = numbers.map((num)=>
<li>{num*2}</li>
);
return(
<ul>{setItems}</ul>
)
}
const numbers = [2,3,4,5,6]
ReactDOM.render(
<SetArray numbers={numbers} />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
由上面的例子我们还可以看出,如果我们要更改numbers里面的数值,要在花括号{}里面进行计算,而不是写成{num}*2这样!!!

但是,我们运行上面的例子的时候,会看到提示说:Warning: Each child in an array or iterator should have a unique "key" prop.
这就是告诉我们要有一个key值,所以我们要给他加上:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function SetArray(props){
const numbers = props.numbers;
const setItems = numbers.map((num)=>
<li key={num.toString()}>{num*2}</li>
);
return(
<ul>{setItems}</ul>
)
}
const numbers = [2,3,4,5,6]
ReactDOM.render(
<SetArray numbers={numbers} />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
这样就ok啦~!
但是我们要注意,key值她不是随便写的,他只有放在就近的数组上下文中才有意义,比如下面16demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function SetLi(props){
const number = props.num;
return(
<li>{number*2}</li>
)
}
function SetArray(props){
const numbers = props.numbers;
const setItems = numbers.map((num)=>
<SetLi key={num.toString()} num={num} />
);
return(
<ul>{setItems}</ul>
)
}
const numbers = [2,3,4,5,6]
ReactDOM.render(
<SetArray numbers={numbers} />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
在上面的例子中,我们把key放在了引入组件的位置,而不是放在里组件里面,li的属性里面,这就说明,我们的key只对自己所在的上下文是有意义的!否则是没有意义的!
大量的经验告诉我们,凡是写在map()方法里面的,key写在里面是比较合适的~

而且,key并非是全局唯一的,他只是说当两个兄弟元素之间传递的话,必须是唯一的!并且,key是传递给react,不传递给组件的,所以我们F12看元素的时候,是没有key的属性的,
如果我们想要读到key的值,就把他赋值给其他显性属性!

我们上面的例子,都是把map()方法得到的值赋值给一个变量,然后用花括号引入这个变量以渲染,实际上,我们可以直接用花括号里面写上表达式,省去赋值变量这一步,比如17demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function SetLi(props){
const number = props.num;
return(
<li>{number*2}</li>
)
}
function SetArray(props){
const numbers = props.numbers;
return(
<ul>
{
numbers.map((num)=>
<SetLi key={num.toString()} num={num} />
)
}
</ul>
)
}
const numbers = [2,3,4,5,6]
ReactDOM.render(
<SetArray numbers={numbers} />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>


8、表单
在React中,可变状态通常保存在组件的state属性中,并且只能通过使用setState()来更新
所以我们可以把两者结合起来,使React的state成为“唯一数据源。渲染表单的React组件还控制着用户输入过程中表单发生的操作。
被React以这种方式控制取值的表单输入元素就叫做“受控组件”。
如18demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class NameForm extends React.Component{
constructor(props){
super(props);
this.state = {
value:‘‘
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e){
this.setState({
value:e.target.value
})
}
handleSubmit(e){
alert(‘提交的名字是:‘+ this.state.value);
e.preventDefault();
}
render(){
return(
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" onChange={this.handleChange} value={this.state.value} />
</label>
<input type="submit" value="提交" />
</form>
)
}
}
ReactDOM.render(
<NameForm />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
8.1.textarea也可以这样用:
<script type="text/babel">
class NameForm extends React.Component{
constructor(props){
super(props);
this.state = {
namevalue:‘‘,
contvalue:‘请输入你想输的玩楞‘
};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange1(e){
this.setState({
namevalue:e.target.value
})
}
handleChange2(e){
this.setState({
contvalue:e.target.value
})
}
handleSubmit(e){
console.log(‘提交的名字是:‘+ this.state.namevalue);
console.log(‘提交的内容是:‘+ this.state.contvalue);
e.preventDefault();
}
render(){
return(
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" onChange={this.handleChange1} value={this.state.namevalue} />
</label>
<label>
内容:
<textarea onChange={this.handleChange2} value={this.state.contvalue}></textarea>
</label>
<input type="submit" value="提交" />
</form>
)
}
}
ReactDOM.render(
<NameForm />,
document.getElementById(‘root‘)
)
</script>

8.2.select基本上也可以这么用
<script type="text/babel">
class NameForm extends React.Component{
constructor(props){
super(props);
this.state = {
namevalue:‘‘,
contvalue:‘请输入你想输的玩楞‘,
fruitvalue:‘coconut‘
};
this.handleChange1 = this.handleChange1.bind(this);
this.handleChange2 = this.handleChange2.bind(this);
this.handleChange3 = this.handleChange3.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange1(e){
this.setState({
namevalue:e.target.value
})
}
handleChange2(e){
this.setState({
contvalue:e.target.value
})
}
handleChange3(e){
this.setState({
fruitvalue:e.target.value
})
}
handleSubmit(e){
console.log(‘提交的名字是:‘+ this.state.namevalue);
console.log(‘提交的内容是:‘+ this.state.contvalue);
console.log(‘提交的水果是:‘+ this.state.fruitvalue);
e.preventDefault();
}
render(){
return(
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" onChange={this.handleChange1} value={this.state.namevalue} />
</label>
<label>
内容:
<textarea onChange={this.handleChange2} value={this.state.contvalue}></textarea>
</label>
<label>
<select onChange={this.handleChange3} value={this.state.fruitvalue}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">柠檬</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
)
}
}
ReactDOM.render(
<NameForm />,
document.getElementById(‘root‘)
)
</script>

8.3.文件input标签 <input type="file"/>
因为他的value只读,所以它是React中的一个非受控组件。

8.4.处理多个输入 直接上19demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class NameForm extends React.Component{
constructor(props){
super(props);
this.state = {
persons:2,
join:true,
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
const target = e.target;
const value = target.type===‘checkbox‘? target.checked : target.value;
const name = target.name;
//如果下面这个不写的话,是数字的时候,不管点击什么,都不会变化
this.setState({
[name]:value
})
}
render(){
return(
<form>
<label>
参与:
<input name=‘join‘ type="checkbox" onChange={this.handleChange} checked={this.state.join} />
</label>
<label>
人数:
<input name=‘persons‘ type="number" onChange={this.handleChange} value={this.state.persons} />
</label>
</form>
)
}
}
ReactDOM.render(
<NameForm />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
上面的例子主要就是两点,意识给每一个input起一个name名字,这样到时候赋值的时候,就知道给谁了;第二就是要知道ES6里面计算属性名称的语法更新,也就是要把name包起来,就是【name】

9、状态提升
这是个大工程呀,其实就是说,当两个组件公用一个state时,我们把这个state提升到两个子组件的父组件里面,这样就比较好渲染啦!
比如我们想做一个华氏度和摄氏度同步更新的功能,那就要用到状态提升了!
如20demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
//首先做一个判断是否水沸
function BoilingVerdict(props){
if(props.celsius >= 100){
return <p>The sater sould boil.</p>
}else{
return <p>The sater would not boil.</p>
}
}
//温度之间的转换 两个函数
function toCelsius(fahrenheit){
return (fahrenheit - 32)*5/9;
}

function toFahrenheit(celsius){
return (celsius * 9 / 5)+32;
}

//一个函数可以接受温度和转换温度的函数
function tryConvert(temperature,convert){
const input = parseFloat(temperature);
if(Number.isNaN(input)){
return ‘‘;
}
const output = convert(input);
const rounded = Math.round(output*1000)/1000;
return rounded.toString();
}

//创建一个用于输入温度的input
const scaleNames = {
c : ‘Celsius‘,
f : ‘Fahrenheit‘
}
class TemperatureInput extends React.Component{
constructor(props){
super(props);
this.state = {
temperature:‘‘
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e){
// this.setState({
// temperature:e.target.value
// })
this.props.onTemperatureChange(e.target.value);
}
render(){
const temperature = this.props.temperature;
const scale = this.props.scale;
return(
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={this.handleChange} />
</fieldset>
)
}
}

class Calculator extends React.Component{
constructor(props){
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: ‘‘, scale: ‘c‘};
}
handleCelsiusChange(temperature){
this.setState ({
scale: ‘c‘,
temperature
})
}
handleFahrenheitChange(temperature){
this.setState ({
scale: ‘f‘,
temperature
})
}
render(){
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === ‘f‘ ?tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === ‘c‘ ? tryConvert(temperature, toFahrenheit) : temperature;
return(
<div>
<TemperatureInput temperature={celsius} onTemperatureChange={this.handleCelsiusChange} scale=‘c‘ />
<TemperatureInput temperature={fahrenheit} onTemperatureChange={this.handleFahrenheitChange} scale=‘f‘ />
<BoilingVerdict celsius={parseFloat(celsius)} />
</div>

)
}
}
ReactDOM.render(
<Calculator />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
比较负责,好好看!!!再看一遍!!!主要是温度之间,只要改变,另一个就改变!!!

10、组合 VS 继承
10.1.包含关系
有些组件无法提前知晓它们子组件的具体内容,我们建议用一个特殊 children prop来将他们的子组件渲染到结果中:
如21demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style type="text/css">
.FancyBorder-yellow{
color: yellow;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function FancyBorder(props){
return(
<div className={‘FancyBorder FancyBorder-‘+props.color}>
//如果是类组件,就要加this.props.children
{props.children}
</div>
)
}
function WelcomeDialog(){
return(
<FancyBorder color="yellow">
<h1>Welocome</h1>
<p>I love u dk zhang !</p>
</FancyBorder>
)
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>
这个例子是告诉我们,当我们引入组件并且里面传入DOM的时候,如果不用{props.children},里面是什么都不显示,
所以必须用{props.children}来包裹住,有点像是VUE的插槽,但是React里面没有插槽的概念。
当然,children不是固定的,如果我们父元素里面又引入了两个子组件,那么我们可以给他们分别起名字,然后再引入:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}

function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}

10.2.特例关系
有时候,我们会把一些组件看做是其它组件的特殊实例,在React中,特殊组件可以通过props定制并渲染一般组件,如22demo:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
function FancyBorder(props){
return(
<div className={‘FancyBorder FancyBorder-‘+props.color}>
{props.children}
</div>
)
}
function Dialog(props){
return(
<FancyBorder color="blue">
<h1>{props.title}</h1>
<p>{props.message}</p>
{props.children}
</FancyBorder>
)
}
//这是第一种写法
// function WelcomeDialog(){
// return(
// <Dialog title="Welocome" message="dk zhang" />
// )
// }
//第二种class写法
class WelcomeDialog extends React.Component{
constructor(props){
super(props);
this.state = {
login : ‘‘
};
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
}
handleChange(e){
this.setState({
login : e.target.value
})
}
handleSignUp(){
alert(`Welcome aboard,${this.state.login}`)
}
render(){
return(
<Dialog title="Welocome" message="dk zhang">
<input type="text" onChange={this.handleChange} value={this.state.login} />
<button onClick={this.handleSignUp}>Sign Me Up !</button>
</Dialog>
)
}
}
ReactDOM.render(
<WelcomeDialog />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

22demo后期【200413】做了微调,可以康康~
下面做一个大demo,做一个搜索功能 23demo:

11、dangerouslySetInnerHTML 和 style属性
11.1.当我们在防止XSS【脚本】攻击的时候,希望获取到某个表达式的内容,但React.js会把表达式里面的标签给转义,从而达不到我们的需求,这就需要用到dangerouslySetInnerHTML了!
例如:由于这里是后来添加的,所以没有demo,直接写下面吧:
class Editor extends Component {
constructor() {
super()
this.state = {
content: ‘<h1>React.js 小书</h1>‘
}
}

render () {
return (
<div className=‘editor-wrapper‘>
{this.state.content} //<h1>React.js 小书</h1>
</div>
)
}
}
这个时候,我们可以得到的是,他渲染出来的带有标签,那我们加上dangerouslySetInnerHTML:
class Editor extends Component {
constructor() {
super()
this.state = {
content: ‘<h1>React.js 小书</h1>‘
}
}

render () {
return (
<div className=‘editor-wrapper‘>
dangerouslySetInnerHTML={{__html:this.state.content}}
</div>
)
}
}
这样就可以了!不过我们不推荐使用~~~

11.2.React.js里面的style和普通的是不一样的:
<h1 style=‘font-size: 12px; color: red;‘>React.js 小书</h1>
我们react里面,要先变成一个对象,再传进去,并且不能有带【-】的属性,统一改成驼峰式命名:
<h1 style={{fontSize: ‘12px‘, color: ‘red‘}}>React.js 小书</h1>
我们可以用 props 或者 state 中的数据生成样式对象再传给元素,然后用 setState 就可以修改样式:
<h1 style={{fontSize: ‘12px‘, color: this.state.color}}>React.js 小书</h1>


12、react的生命周期函数

我们把react.js将组件渲染,并且构造DOM元素然后塞入页面的过程,称为组件的挂载

每一个组件都有自己的生命周期函数,当组件实例被创建并且会插入到DOM中的时候,下面周期函数会被调用:
一、constructor
二、componentWillMount
三、render
四、componentDidMount
改变组件的state或props会导致更新,当重新渲染组件时会调用下面这些函数:
一、componentWillReceiveProps
二、shouldComponentUpdate
三、componentWillUpdate
四、render
五、componentDidUpdate
当组件从DOM中移除,会调用下面函数:
一、componentWillUnmount

下面我们一一说明:
12.1.render() ---- render方法是必须的,render的返回值是下面的类型:
(1)react元素:要么是自定义的组件,要么是原生的DOM组件
(2)字符串或者数字:会被渲染成DOM中的文本节点
(3)portals:通过ReactDom.createPortal创建
(4)null:什么都不会渲染
(5)Bollean:什么都不渲染
(6)包含多个元素的数组 ---- render(){return [<li key=‘1‘>1</li>,<li key=‘1‘>1</li>]}
render方法应该是简单的,在render中【不能】修改组件的【state】,每一次调用render都会返回一个新的结果。并且在render中也【不能】与【浏览器】进行交互,
如果需要与浏览器交互,就在【componentDidMount】或者其他生命周期函数中进行。

12.2.constructor(props) --- react组件的构造函数在组件装载之前调用。如果没有显示的定义construcor,那么实例化组件时会调用默认的constructor,
如果在React.Component的子类中显示的定义了constructor,那么就要在constructor中最开始调用super(props)。
在构造函数实例化state是一个很好的选择。
在react中使用props初始化state是合法的,但是这存在一个问题:当props被更新时,state并不会被更新。解决的办法是:在组件的componentWillReceiveProps(nextProps)
中用新的props更新state。虽然这能解决问题,但是并不推荐,推荐把state提升到最近的公共父组件中。

12.3.componentWillMount() ---- 当装载发生之前会立即调用componentWillMount。componentWillMount会在调用render之前被调用,
所有在componentWillMount中修改的state,不会导致组件的重新渲染。服务器端渲染才会调用这个方法,
所以推荐通过constructor代替这个方法。

12.4.componentDidMount() ---- 当组件被装载完成会立即出发componentDidMount,在这个函数中修改state会导致组件重新渲染。组件被装载之后才能
操作DOM。如果你需要加载远程数据,在这个地方发送网络请求是个不错的主意。

12.5.componentWillReceiveProps(nextProps) --- 当已经被转载的组件接受新的props之前componentWillReceiveProps会被触发。如果你需要更新state
去响应props的更新,可以在这里通过setState方法更新state。当组件首次接受到props,这个方法不会被调用。
注意:props没有被改变也可能会调用这个方法,所以在这个方法中将当前的props去next props进行比较是很有必要的。

12.6.shouldComponentUpdate(nextState,nextProps) --- 当新的props或state被接受,在渲染之前会调用shouldComponentUpdate,这个方法默认是返回true,
初次渲染和使用forceUpdate,不会调用这个方法。如果shouldComponentUpdate返回false,之后的
componentWillUpdate,render以及componentDidMount不会被盗用,组件以及他的子组件不会被重新渲染。

12.7.componentWillUpdate(nextProps,nextState) --- 当接受到新的props或state,在重新渲染之前会立即调用这个方法。在这个方法中不能this.setState()
初次渲染不会调用这个方法。

12.8.componentDidMount(prevProps,prevState) --- 当更新完成之后会立即调用这个方法,初次渲染不会调用这个方法。当组件被更新之后可以在这里操作DOM,
当你发现现在的props与之前的props不一样,在这个发送网络请求是个不错的主意。

12.9.componentWillUnmount() ---- 组件被摧毁之前会立即调用这个方法,可以在这个方法中做一些必要的清理。

然后我们要明白他和vue的生命周期函数的区别:
beforeCreate:el和data并未初始化;
created:完成了data数据的初始化,el没有
beforeMount:完成了el和data初始化
mounted:完成挂载
beforeUpdate:注意这里是指view层的数据变化前,不是data中的数据变化前触发。
updated:更新完成
beforeDestroy:销毁前
destroyed:当前组件被删除,清空相关内容 ---- 注意,销毁完成后,我们再重新改变message的值,vue不在对此动作进行响应了。但是原先生成的dom元素还在,
可以这么理解:执行了destroy操作,后续就不再受vue控制了,因为这个vue实例已经不存在了。

我们写一个时钟的例子,这里用到了componentWillMount和componentWillUnmount
如27demo,这里主要注意的就是一个清楚定时器的操作


13、PropType -- 可以设置组件的属性值的属性,比如阮一峰的demo06 ,我们这里举一个例子,加入我们控制了组件的title属性必须是字符串24demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script src="../gitRuan/react-demos-master/build/prop-types.js"></script>
<script type="text/babel">

class Title extends React.Component{
static propTypes = {
title:PropTypes.string.isRequired,
}
render(){
return(
<div>
<h1>这里来个标题</h1>
<h2>TITLE: {this.props.title}.</h2>
</div>
)
}
}

var title = ‘111‘;
ReactDOM.render(
<Title title={title} />,
document.getElementById(‘root‘)
)
</script>
</body>

这里要注意 想要用propTypes,就必须引入相应的js,或者是cnpm相应的依赖 不然是会报错的:peoptype is not defined!!!
此外,getDefaultProps方法可以设置属性的默认值,如25demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class Title extends React.Component{
render(){
return(
<div>
<h1>这里来个标题</h1>
<h2>TITLE: {this.props.title}.</h2>
</div>
)
}
}
Title.defaultProps = {
title : ‘hello world‘
}
ReactDOM.render(
<Title />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这里要注意 defaultProps只能写在class外 这是es6的规定 另外写的时候 也不是getDefaultProps 而是defaultProps 切记!

14、获取真实的DOM
组件并不是真实的DOM节点,而是存在于内存之中的一种数据结构,焦作虚拟DOM(virtual DOM)。只有当它插入文档后,才会变成真实的DOM。
根据React的设计,所有DOM变动,都现在虚拟DOM上发生,然后再将实际发生变动的部分,反映在真实DOM上,这种算法叫做DOM diff。
但是,有时候需要从组建获取真实DOM的节点,这就需要用到ref属性 26demo <!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="root"></div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 生产环境中不建议使用 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class Title extends React.Component{
constructor(props) {
super(props);
this.myTextInput = React.createRef();
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
this.refs.myTextInput.focus();
}
render(){
return(
<div>
<h1>这里来个输入框</h1>
<input type="text" ref="myTextInput" />
<input type="button" value="inputGetFoucus" onClick={this.handleClick} />
</div>
)
}
}
ReactDOM.render(
<Title />,
document.getElementById(‘root‘)
)
</script>
</body>
</html>

这里注意,要用ref的时候,要在constructor里面先声明一下,也就是【React.createRef()】

我们可以借用ref来实现点击空白弹窗消失的功能,用到了contains()方法
contains 判断某个元素是不是选定元素的子元素或者本身。
https://blog.csdn.net/boysky0015/article/details/88933184

15、项目实战
15.1.todoList 一个输入后插入删除的小demo
react-todoList小demo--https://juejin.im/post/5c945755f265da611c557076
是myProject
我们按照这个demo来做一遍:

1、安装脚手架:cnpm i create-react-app -g
2、查看自己是否安装脚手架:create-react-app --version
3、创建项目名:create-react-app todolist
4、进入项目:cd todolist
5、跑项目:cnpm start
6、打开 localhost:3000 查看页面

15.2.评论功能 --- commentapp 来源:http://huziketang.mangojuice.top/books/react/lesson14
首先也是 create-react-app commentapp
然后进去commentapp

这里报了一个错:npm ERR! Unexpected end of JSON input while parsing near
本来教程又说要去删除一些文件:C:Users当前计算机用户名字AppDataRoaming pm-cache
但是经我发现,只需要将npm永远指向淘宝镜像就ok了,也就是:
npm config set registry https://registry.npm.taobao.org
-- 配置后可通过下面方式来验证是否成功
npm config get registry
-- 或npm info express
如果不成功,再使用上述方法

我们遵循一个原则:如果一个文件导出的是一个类,那么这个文件名就用大写开头。四个组件类文件导出都是类,所以都是大写字母开头。

16、代码分割
15.1.React.lazy & Suspense
相当于是懒加载,用的时候要先引入插件
import {lazy} from React
我们写一个例子,就在我们的todolist项目吧
我们新建一个testLazy1.js和testLazy2.js,里面的内容是一样的:
import React from ‘react‘;
// Fragment 是一种占位符形式,类似于 Vue 的 Template
import {Component} from ‘react‘;


class TestLazy1 extends Component{
render(){
return(
<div>
<h1>LazyTest 组件</h1>
</div>
)
}
}
export default TestLazy1;
然后在Todolist.js里面通过lazy的方式引入:
import React from ‘react‘;
// Fragment 是一种占位符形式,类似于 Vue 的 Template
import {Component,Fragment,lazy,Suspense} from ‘react‘;

//引入组件
import Todoitem from ‘./Todoitem.js‘


//懒加载试验
const TestLazy1 = lazy(() => import(‘./components/testLazy1.js‘));
const TestLazy2 = lazy(() => import(‘./components/testLazy2.js‘));

class Todolist extends Component{
//懒加载函数
fallback=()=>{
return(
<div>Loadin··</div>
)
}
constructor(props){
super(props);
this.state={
inputValue:‘‘,
list:[]
}
this.handleChange = this.handleChange.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
handleChange(e){
console.log(e.target.value);
const value = e.target.value;
this.setState(()=>({
inputValue:value
}))
}
handleClick(){
const list = this.state.list;
const inputValue = this.state.inputValue;
this.setState(()=>({
list:[...list,inputValue],
inputValue:‘‘
}))
}
handleDelete(index){
//加三个···相当于拼接
const list = [...this.state.list];
list.splice(index,1);
this.setState(()=>({
list:list
}))
}
getTodoItem(){
return this.state.list.map((item,index)=>{
return(
<Todoitem key={index} index={index} item={item} handleDelete={this.handleDelete}></Todoitem>
)
})
}
render(){
return(
<Fragment>
<div>
<input type="text" value={this.state.inputValue} onChange={this.handleChange} />
<button onClick={this.handleClick}>提交</button>
</div>
<ul>
{this.getTodoItem()}
</ul>
<div>
<Suspense fallback={this.fallback()}>
<h1>懒加载组件</h1>
<TestLazy1 />
<TestLazy2 />
</Suspense>
</div>
</Fragment>
)
}
}

export default Todolist;
然后执行:cnpm run build【注意和cnpm start不一样,这是打包】,发现虽然是同样的内容,但是他们生成了两个js,分别是:
3.fae2c000.chunk.js和4.a0ae15a6.chunk.js

方便起见,我们还是把build给删掉,以后需要的时候再打包

17、状态提升
当我们的同级组件或者是其他地方需要用到这个组件的属性的时候,我们要选择用状态提升,把这个属性提升到和其它组件共同的父组件中,用props提供,再用props获取

18、propTypes 和组件参数验证
当我们获取参数的时候,往往因为没有设置类型而出现bug,而在js这种弱类型语言中不会报错,只会运行错误,但React这种就会直接报错,因为他支持强类型语言。js为了弥补缺陷,开发了typescript,而react为了规避这个问题,就要引入一个插件:cnpm install prop-types --save
然后就可以在static propTypes里面设置类型:
import React, { Component } from ‘react‘
import PropTypes from ‘prop-types‘

class Comment extends Component {
static propTypes = {
comment: PropTypes.object
}

render () {
const { comment } = this.props
return (
<div className=‘comment‘>
<div className=‘comment-user‘>
<span>{comment.username} </span>:
</div>
<p>{comment.content}</p>
</div>
)
}
}
有的时候,我们虽然传入元素了,但是不一定每一个参数都有属性值,有时候可能没有传入,这个时候就会报错说没有值或者undefined。
这个时候,我们就可以用过isRequired来强制组件的某个参数必须传入:
...
static propTypes = {
comment: PropTypes.object.isRequired
}
...
这样虽然不会避免报错,但是可以让我们报错的内容更加直观明朗

而且我们可以通过设置defaultProps来设置默认参数值,比如25demo:
class Title extends React.Component{
render(){
return(
<div>
<h1>这里来个标题</h1>
<h2>TITLE: {this.props.title}.</h2>
</div>
)
}
}
Title.defaultProps = {
title : ‘hello world‘
}
ReactDOM.render(
<Title />,
document.getElementById(‘root‘)
)


绑定事件传参的方法:https://blog.csdn.net/hl_qianduan/article/details/89372881

19、高阶组件
高阶组件就是一个函数【不是组件】,传给他一个组件,它返回一个新的组件。
新的组件使用传入的组件作为子组件。
高阶组件的作用是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过props传递信息

高阶组件有助于提高我们的代码灵活性、逻辑复用性。
具体实操还是看我们的评论小项目,就是我们的commentapp项目
实际上就是把getItem setItem这样的数据,或者是用ajax调用的,变成一个组件,然后重复利用

20、context
一个组件可以通过getChildContext方法返回一个对象,这个对象就是子树的context,提供context的组件必须提供childContextTypes作为context的声明和验证。
如果一个组件设置了context,那么它的子组件都可以直接访问到里面的内容,他就像这个组件为根的子树的全局变量。
任意深度的子组件都可以通过contextTypes来生命你想要的context里面的哪些状态,然后可以通过this.context访问到那些状态
假设我们的组件关系是这样的:
class Index extends Component {
static childContextTypes = {
themeColor:PropTypes.string
}
constructor(props){
super(props)
this.state={
themeColor:red
}
}

getChildContext(){
return{
themeColor:this.state.themeColor
}
}

render () {
return (
<div>
<Header />
<Main />
</div>
)
}
}

class Header extends Component {
render () {
return (
<div>
<h2>This is header</h2>
<Title />
</div>
)
}
}

class Main extends Component {
render () {
return (
<div>
<h2>This is main</h2>
<Content />
</div>
)
}
}

class Title extends Component {
static ContextTypes = {
themeColor:PropTypes.string
}
render () {
return (
<h1 style={{color:this.context.themeColor}}>React.js 小书标题</h1>
)
}
}

class Content extends Component {
render () {
return (
<div>
<h2>React.js 小书内容</h2>
</div>
)
}
}

ReactDOM.render(
<Index />,
document.getElementById(‘root‘)
)

21、redux react-redux
注意redux和react-redux是不一样的,redux只是代表一种架构模式(Flux架构的一种变种),他不关注你到底用什么库,可以是react,也可以是vue,甚至可以是jquery
而react-redux就是redux这种架构模式和react.js结合起来的一个库,就是redux架构在react.js中的体现。

21.1.我们通过项目来学习redux,创建一个叫make-redux,
我们修改index里面的结构,修改index.js里面的内容

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
</head>
<body>
<div id="root">
<div id="title"></div>
<div id="content"></div>
</div>
</body>
</html>


import ‘./index.css‘;
let appState = {
title:{
text:‘React.js 小书‘,
color:‘red‘,
},
content:{
text:‘React.js 小书内容‘,
color:‘blue‘,
}
}

function dispatch(action){
switch(action.type){
case ‘UPDATE_TITLE_TEXT‘:
appState.title.text = action.text
break
case ‘UPDATE_TITLE_COLOR‘:
appState.title.color = action.color
break
default:
break
}
}

function renderApp(appState){
renderTitle(appState.title);
renderContent(appState.content);
}

function renderTitle(title){
const titleDOM = document.getElementById(‘title‘);
titleDOM.innerHTML = title.text;
titleDOM.style.color = title.color
}

function renderContent(title){
const contentDOM = document.getElementById(‘content‘);
contentDOM.innerHTML = title.text;
contentDOM.style.color = title.color
}

renderApp(appState);//首次渲染页面
dispatch({type:‘UPDATE_TITLE_TEXT‘,text:‘《React.js小书》‘})//修改标题文本
dispatch({type:‘UPDATE_TITLE_COLOR‘,color:‘blue‘})//修改标题颜色
renderApp(appState);//把新的数据渲染到页面上

然后我们把dispatch和appState给放到store里面,怎么放呢?就是添加一个createStore函数在index.js,他是串门用来生产state和dispatch的集合:
function createStore (state,statechanger){
const getState = ()=> state
const dispatch = (action) => stateChanger(state,action)
return {getState,dispatch}
}

然后渲染数据的方式不变,修改数据生成的方式
const store = createStore(appState,stateChanger);

renderApp(store.getState());//首次渲染页面
store.dispatch({type:‘UPDATE_TITLE_TEXT‘,text:‘《React.js小书》‘})//修改标题文本
store.dispatch({type:‘UPDATE_TITLE_COLOR‘,color:‘blue‘})//修改标题颜色
renderApp(store.getState());//把新的数据渲染到页面上

我们同时有希望这个数据是可以自己检测到变化而自动更新的,而不是每次都要调用一次renderApp()。那就是用监听模式:
import ‘./index.css‘;

let appState = {
title:{
text:‘React.js 小书‘,
color:‘red‘,
},
content:{
text:‘React.js 小书内容‘,
color:‘blue‘,
}
}

function stateChanger(state,action){
switch(action.type){
case ‘UPDATE_TITLE_TEXT‘:
state.title.text = action.text
break
case ‘UPDATE_TITLE_COLOR‘:
state.title.color = action.color
break
default:
break
}
}

function createStore (state,stateChanger){
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = ()=> state
const dispatch = (action) => {
stateChanger(state,action)
listeners.forEach((listener)=>listener())
}
return {getState,dispatch,subscribe}
}

function renderApp(appState){
renderTitle(appState.title);
renderContent(appState.content);
}

function renderTitle(title){
const titleDOM = document.getElementById(‘title‘);
titleDOM.innerHTML = title.text;
titleDOM.style.color = title.color
}

function renderContent(title){
const contentDOM = document.getElementById(‘content‘);
contentDOM.innerHTML = title.text;
contentDOM.style.color = title.color
}

const store = createStore(appState,stateChanger);
store.subscribe(()=>renderApp(store.getState()))//监听数据变化

renderApp(store.getState());//首次渲染页面
store.dispatch({type:‘UPDATE_TITLE_TEXT‘,text:‘《React.js小书》‘})//修改标题文本
store.dispatch({type:‘UPDATE_TITLE_COLOR‘,color:‘blue‘})//修改标题颜色
//renderApp(store.getState());//把新的数据渲染到页面上

完整的index.js:
import ‘./index.css‘;
function reducer(state,action){
if(!state){
return{
title:{
text:‘React.js 小书‘,
color:‘red‘,
},
content:{
text:‘React.js 小书内容‘,
color:‘blue‘,
}
}
}
switch(action.type){
case ‘UPDATE_TITLE_TEXT‘:
return{//构建新的对象并返回
...state,
title:{
...state.title,
text:action.text
}
}
case ‘UPDATE_TITLE_COLOR‘:
return{//构建新的对象并返回
...state,
title:{
...state.title,
color:action.color
}
}
default:
return state//没有修改,返回原来的对象
}
}

function createStore (reducer){
let state = null;
const listeners = [];
const subscribe = (listener) => listeners.push(listener);
const getState = ()=> state
const dispatch = (action) => {
state=reducer(state,action) //覆盖原对象
listeners.forEach((listener)=>listener())
}
dispatch({});//初始化state
return {getState,dispatch,subscribe}
}

function renderApp(newAppState,oldAppState=
{}){
if(newAppState === oldAppState)return //数据没有变化就不渲染了
console.log(‘render App‘);
renderTitle(newAppState.title);
renderContent(newAppState.content);
}

function renderTitle(newTitle,oldTitle={}){
if(newTitle === oldTitle)return //数据没有变化就不渲染了
console.log(‘render Title‘);
const titleDOM = document.getElementById(‘title‘);
titleDOM.innerHTML = newTitle.text;
titleDOM.style.color = newTitle.color
}

function renderContent(newContent,oldContent={}){
if(newContent === oldContent)return //数据没有变化就不渲染了
console.log(‘render content‘);
const contentDOM = document.getElementById(‘content‘);
contentDOM.innerHTML = newContent.text;
contentDOM.style.color = newContent.color
}

const store = createStore(reducer);
let oldState = store.getState();
store.subscribe(()=>{
const newState = store.getState();//数据可能变化,获取新的state
renderApp(newState,oldState)//把新旧的state传进去渲染
oldState = newState; // 渲染完以后,新的newState变成了旧的oldState,等待下一次数据变化重新渲染
})//监听数据变化

renderApp(store.getState());//首次渲染页面
store.dispatch({type:‘UPDATE_TITLE_TEXT‘,text:‘《React.js小书》‘})//修改标题文本
store.dispatch({type:‘UPDATE_TITLE_COLOR‘,color:‘blue‘})//修改标题颜色
//renderApp(store.getState());//把新的数据渲染到页面上

21.2.react-redux
其实这里就是把context和store结合的地方
首先我们还是新建一个create-react-app react-redux
进入项目:cd react-redux
然后引入:cnpm install prop-types --save

21.2.1.import引入时的{}问题
当我们引入的是export default XX;类型的文件时,可以不用{}
而且可以自己随意起名字,比如:
import A from ‘a.js‘;
import B from ‘a.js‘;
import fvbbg from ‘a.js‘
都是成立的;

但是我们是命名导出为export name的模块时,就要加{}了:
export const A = ···

react-redux这个项目也是为了了解react和redux结合
自己写了createStore 写了themeReducer
在createStore里面做了监听listener和刷新subscribe
另外我们为了Index里面没有context相关,又加了provider在react-redux.js里面
而react-redux里面除了provider之外,还有connect方法,是为了做高阶组件从而使得props传承

22、真正的在项目中使用redux react-redux插件
首先我们再创立一个新的react项目,叫make-react-redux
create-react-app make-react-redux
cd make-react-redux
安装redux 和 react-redux插件:
cnpm install redux react-redux --save
然后把index.js Header.js Content.js themeSwitch.js react-redux.js 全部复制进去,再修改Header.js Content.js themeSwitch.js里面的connect引入方法:
由import { connect } from ‘./react-redux‘; 变成:
import { connect } from ‘react-redux‘;

我们把组件分为Dumb组件,还有Smart组件
凡是不依赖任何外部组件以及react-redux或者redux的组件,叫做Dumb组件,但是我们观察到,我们之前写的Header.js也好,content.js也好,都是本来是Dumb组件的,因为有了connect,从而用了react-redux,成了Smart组件

import React,{Component} from ‘react‘;
import PropTypes from ‘prop-types‘;
import { connect } from ‘react-redux‘;

class Header extends Component{
static propTypes = {
themeColor:PropTypes.string,
}

render(){
return(
<h1 style={{color:this.props.themeColor}}>张卫健</h1>
)
}
}

const mapStateToProps = (state) => {
return {
themeColor:state.themeColor
}
}
Header = connect(mapStateToProps)(Header)

export default Header;

为了避免这种问题,我们新建了一个components文件夹,约定它里面就放Dumb组件
另外一个containers文件夹,放Smart组件

Dumb 基本只做一件事情 —— 根据 props 进行渲染。而 Smart 则是负责应用的逻辑、数据,把所有相关的 Dumb(Smart)组件组合起来,通过 props 控制它们

Smart 组件可以使用 Smart、Dumb 组件;而 Dumb 组件最好只使用 Dumb 组件,否则它的复用性就会丧失。

22、用UI框架 【技术胖的视频学习】 --- Ant Design
首先我们要重新开一个项目,然后引入antdesign:
create-react-app antdesign-react
cd antdesign-react
cnpm install antd --save
cnpm install redux --save

我们用全局引入的话,就是在文件里面引入css和需要的组件,这都是全局引入:
import ‘antd/dist/antd.css‘;
import { Input } from ‘antd‘;
import { Button } from ‘antd‘;

按需引入有两种方法,一个是引入的时候,就加上后缀:
import DatePicker from ‘antd/es/date-picker‘; // 加载 JS
import ‘antd/es/date-picker/style/css‘; // 加载 CSS
// import ‘antd/es/date-picker/style‘; // 加载 LESS

一个就是配置 babel-plugin-import
cnpm install babel-plugin-import --save-dev
然后在根目录下新建一个.babelrc文件,这里可以先建立一个txt文件,然后另存为.babelrc文件
修改不了后缀名的话,就在文件夹的组织里面找到【文件夹和搜索选项】,然后【查看】在最下面找到展示后缀名
然后添加如下:
// .babelrc or babel-loader option
{
"plugins": [
["import", {
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css" // `style: true` 会加载 less 文件
}]
]
}
然后引入的时候:
// babel-plugin-import 会帮助你加载 JS 和 CSS
import { DatePicker } from ‘antd‘;

我们想要用redux-devtool工具,就要在chrome里添加这个扩展程序,怎么添加呢?
首先我们要安装一个插件,叫做 谷歌访问助手
下载链接:https://github.com/haotian-wang/google-access-helper
然后我们在本地新建一个文件夹,且就是:D:/chromeHelper
然后把这个链接里面的地址 git clone上去,也即是:
git clone https://github.com/haotian-wang/google-access-helper.git
然后再打开chrome,点击右上角的红色箭头或者三个省略号,
然后点击更多工具,然后是【扩展程序】,
然后点击【加载已解压的扩展程序】,把已经下载好的谷歌访问助手加入进来
点击【扩展程序】左边的三个横线,下面出来【打开chrome应用商店】
去搜索redux-dev添加就可以了
这个时候F12自己的项目,最后面就是redux工具
到这里还不算完,还要在自己的store文件夹里面的index.js,也就是引入reducer的那个文件里,配置:
const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
然后再去看,就有了!
如果我们点击state,就会看到目前所有的数据,也就不用再繁琐的console.log了!


以前:安装babel-plugin-import后只需要按照antd文档中的方式在.babelrc中配置即可。

现在:就算配置了.babelrc也不会按需引入,而且最重要的还不报错。
1、运行 npm run eject (暴露react的配置,不然很多配置不会显示出来,包括我们现在需要的babel配置)
可能会报错,就git status
git add .
git commit -m "init eject"
git commit -m "init eject"
cnpm run eject
但是基本上我写了还是会报错,但是只要后面那是successful,可以运行就可以了!
暂时不要在意这些细节!

2、打开package.json文件,找到

"babel": {
"presets": [
"react-app"
]
添加antd文档中对于babel-plugin-import的配置,修改为:
"babel": {
"presets": [
"react-app"
],
"plugins":
[
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": "css"
}
]
]
3、确保src下没有.babelrc文件,重启项目,一切很OK;

23、axios异步请求 重新建一个项目 在vscode里面运行,并且打开终端,并且使用ant的按需请求【另一种官方方法】
axios的异步请求地址用的是技术胖的:https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList

首先还是新建一个项目create-react-app axios-react
cd axios-react
复制antdesign-react-new项目
cnpm install
cnpm install axios --save
cnpm start


23.1.vscode打开终端只需要[ctrl+`]

23.2.ant的按需请求
按照官方文档:https://ant.design/docs/react/use-with-create-react-app-cn所讲
cnpm install react-app-rewired customize-cra --save

修改package.json文件:
"scripts": {
// "start": "react-scripts start",
"start": "react-app-rewired start",
// "build": "react-scripts build",
"build": "react-app-rewired build",
// "test": "react-scripts test",
"test": "react-app-rewired test"
// "eject": "react-scripts eject"
// "eject": "react-scripts eject"
},

在项目根目录创建一个config-overrides.js,用于修改默认配置
//module.exports = function override(config, env) {
//// do stuff with the webpack config...
//return config;
//};

const { override, fixBabelImports } = require(‘customize-cra‘);
module.exports = override(
fixBabelImports(‘import‘, {
libraryName: ‘antd‘,
libraryDirectory: ‘es‘,
style: ‘css‘,
}),
);

到这里就是成功的!

但是我们为了后期使用ant方便,尤其是修改主题的时候要用到less,所以我们先给他配置成支持less的:
cnpm install less less-loader --save

const { override, fixBabelImports, addLessLoader } = require(‘customize-cra‘);
module.exports = override(
fixBabelImports(‘import‘, {
libraryName: ‘antd‘,
libraryDirectory: ‘es‘,
// style: ‘css‘,
style: true,
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: { ‘@primary-color‘: ‘#1DA57A‘ },
}),
);

成功cnpm start之后,发现我们的主题色变成绿色了!

以上我们完成了两个需求

接下来完成我们的axios异步请求

这里我们首先要在TodoList里面的componentDidMount里面写上axios请求
使用中间件,redux-thunk
cnpm install redux-thunk --save

修改store里面的index配置
import {createStore,applyMiddleware,compose} from ‘redux‘
import reducer from ‘./reducer‘

//引入thunk
import thunk from ‘redux-thunk‘

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));

//const store = createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
const store = createStore(reducer,enhancer);

export default store;
在没有使用中间件之前,我们的actionCreator只能返回对象,加了中间件,就可以返回一个函数

中间件是redux的,不是react的,注意!

redux-saga也是一种中间件!这里我就不再笔记demo了,如果公司有用,就直接 去技术胖的redux-react第19集!


24、react-redux项目 新建react-redux-app
create-react-app react-redux-app;
cd react-redux-app;
cnpm install;
cnpm install redux react-redux --save

当我们用到connect的时候,主要要有一个映射,也就是我们的stateToProps函数,他就是指我们把state属性转化为props,以便在jsx里this.props.XXX变成this.props.XXX
import { connect } from ‘react-redux‘
const VisibleTodoList = connect()(TodoList);
上面代码中,TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件。
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数
输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去。

Provider:用过之后,App的所有子组件就默认都可以拿到state了。

如果我们用到了this.props.XXX方法,但是不知道怎么传参数,就这样:<span onClick={()=>{this.props.deleteList(index)}} value={index}> ==删除</span>

我们还可以通过结构赋值,来替换this.props.inputValue等
但是这里就不再替换了,但是要知道是这样的:
let { inputValue , addList , deleteList , list } = this.props;

24、路由 reactRouter
首先,还是创建一个新的项目
create-react-app router-react-app07
cd router-react-app07
cnpm install react-router-dom --save
cnpm install redux react-redux --save

然后,一个最简单的方式:
import React from ‘react‘;
import {BrowserRouter as Router,Route,Link} from ‘react-router-dom‘;

function App(){
return (
<div className="App">
sadf sadf
</div>
);
}

function List(){
return (
<h2>我是list</h2>
)
}

function AppRouter(){
return (
<Router>
<ul>
<li><Link to=‘/‘>首页</Link></li>
<li><Link to=‘/list/‘>列表</Link></li>
</ul>
<Route path=‘/‘ exact component={App} />
<Route path=‘/list/‘ exact component={List} />
</Router>
)
}
export default AppRouter;

24.1.重定向
重定向可以让你的页面打开时,不能再前进后退
import React,{Component} from ‘react‘;
import {Link,Redirect} from ‘react-router-dom‘;

class Index extends Component{
constructor(props){
super(props);
this.state={
list:[
{cid:123,title:‘刘璐的博客-1‘},
{cid:456,title:‘刘璐的博客-2‘},
{cid:789,title:‘刘璐的博客-3‘}
]
}
}
render(){
return(
<div>
//必须放在return里面
<Redirect to="/home/" />
<h2>我是首页</h2>
<ul>
{
this.state.list.map((item,index)=>{
return(
<li key={index}>
<Link to={‘/list/‘+item.cid}>
{item.title}
</Link>
</li>
)
})
}
</ul>
</div>
)
}
}
export default Index;

24.2.编程式重定向
import React,{Component} from ‘react‘;
import {Link,Redirect} from ‘react-router-dom‘;

class Index extends Component{
constructor(props){
super(props);
this.state={
list:[
{cid:123,title:‘刘璐的博客-1‘},
{cid:456,title:‘刘璐的博客-2‘},
{cid:789,title:‘刘璐的博客-3‘}
]
}
this.props.history.push(‘/home/‘)
}
render(){
return(
<div>
//必须放在return里面
//<Redirect to="/home/" />
<h2>我是首页</h2>
<ul>
{
this.state.list.map((item,index)=>{
return(
<li key={index}>
<Link to={‘/list/‘+item.cid}>
{item.title}
</Link>
</li>
)
})
}
</ul>
</div>
)
}
}

export default Index;

25、嵌套路由
这里我就不再用redux了
我们做一个真正的小demo,就是router-react-pro07
create-react-app router-react-pro07
cd router-react-pro07
cnpm install react-router-dom --save

25.2.动态获取路由进行配置
这意思是后台传给我们路由,而不是自己写死的三个或者四个,这个在07里面有实现


26、react hooks --- 不用class声明组件,而是用函数方法声明组件
首先我们新建一个文件夹,叫react-hooks-demo08
然后我们要注意,用hooks,就要用useState,这要求我们的版本一定是16.8以上的
然后我们首先写一个点击数量加一的例子,也就是我们的app.js里面的例子,里面必须先引入{useState}

26.1.useState
const [count , setCount] = useState(0);
上面就是在应用useState的实例
useState(0)里面的0就是一个初始值
而const [count , setCount]代表es6的数组结构,如果不用es6,就要写成:
let _useState = useState(0);
let count = _useState[0];
let setCount = _useState[1];

为了更清楚的了解,我们建立Example2.js
import React,{useState} from ‘react‘;function Example2(){
const [age , setAge] = useState(24);
const [sex , setSex] = useState(‘female‘);
const [work , setWork] = useState(‘web前端‘);

return (
<div>
<p>刘璐今年:{age}岁</p>
<p>刘璐是一个:{sex}孩子</p>
<p>刘璐是一名:{work}职业</p>
</div>
)
}

export default Example2;

26.2.用useEffect()代替生命周期
首先我们新建一个example3.js
事实上,在example3.js里面,他对应的应该是下面两个周期函数:
componentDidMount(){
console.log(`componentDidMount => You clicked ${this.state.times} times`)
}
componentDidUpdate(){
console.log(`componentDidUpdate => You clicked ${this.state.times} times`)
}
统一变成了:
useEffect(()=>{
console.log(`useEffect => You clicked ${time} times`)
})

当我们写成这样的时候:
import React,{useState,useEffect} from ‘react‘;
import {BrowserRouter as Router, Route, Link} from ‘react-router-dom‘;

function OtherIndex(){
useEffect(()=>{
console.log(‘useEffect => 老弟,你来了我们OtherIndex页面!‘);
return ()=>{
console.log(‘老弟,你走了我们OtherIndex页面!‘)
}
})
return(
<div>我是另一个主页</div>
)
}

function OtherList(){
useEffect(()=>{
console.log(‘useEffect => 老弟,你来了我们OtherList页面!!‘);
return ()=>{
console.log(‘老弟,你走了我们OtherList页面!‘)
}
})
return(
<div>我是一个列表页</div>
)
}

function Example3(){
const [time,setTime] = useState(0);
useEffect(()=>{
console.log(`useEffect => You clicked ${time} times`)
})
return(
<div>
<p>u already clicked {time} times.</p>
<button onClick={()=>{setTime(time+1)}}>点击加一</button>
</div>
)
}

function RouterIndex(){
return(
<Router>
<ul>
<li><Link to=‘/OtherIndex‘>OtherIndex</Link></li>
<li><Link to=‘/OtherList‘>OtherList</Link></li>
</ul>
<Route path=‘/OtherIndex‘ component={OtherIndex} />
<Route path=‘/OtherList‘ component={OtherList} />
</Router>
)
}

export default RouterIndex;

当我们点击OtherList的时候,会一直出现老弟,你走了我们OtherIndex页面!说明他不只有在销毁的时候出现,而是只要不在这个页面,都会出现,这显然不符合我们的要求

然后我们新建一个example4.js,升级example3.js
import React,{useState,useEffect} from ‘react‘;
import {BrowserRouter as Router, Route, Link} from ‘react-router-dom‘;

function OtherIndex(){
useEffect(()=>{
console.log(‘useEffect => 老弟,你来了我们OtherIndex页面!‘);
return ()=>{
console.log(‘老弟,你走了我们OtherIndex页面!‘)
}
},[])//这里加一个空的数组的参数,表明我们只有解绑的时候,才会出现销毁提示,只有第一次进来的时候,才会出现提示
return(
<div>我是另一个主页</div>
)
}

function OtherList(){
useEffect(()=>{
console.log(‘useEffect => 老弟,你来了我们OtherList页面!!‘);
return ()=>{
console.log(‘老弟,你走了我们OtherList页面!‘)
}
},[])
return(
<div>我是一个列表页</div>
)
}

function RouterIndex(){
return(
<Router>
<ul>
<li><Link to=‘/OtherIndex‘>OtherIndex</Link></li>
<li><Link to=‘/OtherList‘>OtherList</Link></li>
</ul>
<Route path=‘/OtherIndex‘ component={OtherIndex} />
<Route path=‘/OtherList‘ component={OtherList} />
</Router>
)
}

export default RouterIndex;

但是我们的点击次数加一的例子来讲,他必须每次都出现,假如我们给他一个空数组,他就只出现一次,怎么办呢?
这个时候,就要注意了,虽然我们可以传入空数组,但是同时,我们也可以不传入空数组啊,我们给他一个传值就OK拉!
function Example3(){
const [time,setTime] = useState(0);
useEffect(()=>{
console.log(`useEffect => You clicked ${time} times`)
},[time])
return(
<div>
<p>u already clicked {time} times.</p>
<button onClick={()=>{setTime(time+1)}}>点击加一</button>
</div>
)
}

26.3.useContext()--- 插件是createContext useContext ,相当于是我们的context的hooks用法,也就是让数据公共化
这里我们创建example5.js
import React,{useState , createContext , useContext} from ‘react‘;

const countContext = createContext();

function Counter(){
let counter = useContext(countContext);
return(
<div>{counter}</div>
)
}

function Example5(){
const [time,setTime] = useState(0);
return(
<div>
<p>u already clicked {time} times.</p>
<button onClick={()=>{setTime(time+1)}}>点击加一</button>
<countContext.Provider value={time}>
<Counter/>
</countContext.Provider>
</div>
)
}
export default Example5;

26.4.useReducer 经常和useContext联合使用
我们例子是:example6.js,没有使用context
import React,{useState , useReducer} from ‘react‘;

function ReducerDemo(){
const [count , dispatch] = useReducer((state,action) => {
switch(action){
case ‘add‘:
return state+1
case ‘sub‘:
return state-1
default:
return state
}
},0);
return(
<div>
<p>现在的分数是:{count}分</p>
<button onClick={()=>{dispatch(‘add‘)}}>点击加一</button>
<button onClick={()=>{dispatch(‘sub‘)}}>点击减一</button>
</div>
)
}

export default ReducerDemo;

然后我们建一个reducer hooks的小案例,为了让createContext和useContext和useReducer合作,叫做 course 文件夹 D:MyProject odejs26reactmyProject eact-hooks-demo08course
然后我们先新建一个showArea.js
里面是:
import React from ‘react‘;
function ShowArea(){
return (
<div style = {{color:"blue"}}>字体颜色blue</div>
)
}
export default ShowArea;

然后新建Buttons.js
import React from ‘react‘;
function Buttons(){
return (
<div>
<button>红色</button>
<button>黄色</button>
</div>
)
}
export default Buttons;
然后新建一个统一他们的example7.js
import React from ‘react‘;
import Bottons from ‘./Buttons‘
import ShowArea from ‘./showArea‘
function Example7(){
return (
<div>
<ShowArea />
<Bottons />
</div>
)
}
export default Example7;
然后新建一个color的颜色转换reducer,因为暴露的多,所以不采用export default的写法,而是直接export const
import React , {createContext , useReducer} from ‘react‘;

export const ColorContext = createContext({});

export const UPDATE_COLOR = "UPDATE_COLOR";

const reducer = (state , action)=>{
switch(action.type){
case UPDATE_COLOR:
return action.color
default:
return state
}
}

export const Color = props => {
const [color , dispatch] = useReducer(reducer,‘blue‘)
return (
<ColorContext.Provider value={{color,dispatch}}>
{props.children}
</ColorContext.Provider>
)
};
然后修改example7.js
import React from ‘react‘;
import Bottons from ‘./Buttons‘
import ShowArea from ‘./showArea‘
import { Color } from ‘./color‘
function Example7(){
return (
<div>
<Color>
<ShowArea />
<Bottons />
</Color>
</div>
)
}
export default Example7;
然后修改Buttons.js
import React , {useContext} from ‘react‘;
import {ColorContext , UPDATE_COLOR} from ‘./color‘
function Buttons(){
const {dispatch} = useContext(ColorContext)
return (
<div>
<button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"red"})}}>红色</button>
<button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"yellow"})}}>黄色</button>
</div>
)
}
export default Buttons;
然后修改showArea.js
import React , {useContext} from ‘react‘;
import { ColorContext } from ‘./color‘;
function ShowArea(){
const {color} = useContext(ColorContext);
return (
<div style = {{color:color}}>字体颜色{color}</div>
)
}
export default ShowArea;
就ok拉 !

26.5.useMemo -- 解决性能问题
一般情况下,我们有一个父组件和子组件,当我们的状态改变时,会调用这个生命周期函数:shouldComponentUpdate
但是当我们用hooks时【一般情况下也是如此】,我们会发现,当我们的子组件发生变化时,我们的父组件也会发生状态重新渲染【当然我说的有点主观了】大概就是下面的代码:
import React,{useState} from ‘react‘;
function Example8(){
const [junxian,setJunxian] = useState(‘林俊贤在接客状态‘);
const [dkzhang,setDkzhang] = useState(‘张卫健在接客状态‘);
return(
<div>
<button onClick={()=>{setJunxian(new Date().getTime())}}>林俊贤</button>
<button onClick={()=>{setDkzhang(new Date().getTime()+‘DK向我走来!‘)}}>张卫健</button>
<ChildComponent name={junxian}>{dkzhang}</ChildComponent>
</div>
)
}

function ChildComponent({name , children}){
function changeJunxian(){
console.log(‘他来了!!!林俊贤向我走来了!!!‘)
return name+‘,林俊贤向我走来了!‘
}
const actionJunxian = changeJunxian(name);
return(
<div>
<div>{actionJunxian}</div>
<div>{children}</div>
</div>
)
}
export default Example8;

这个时候,当我们刚进去的时候,控制台会有【他来了!!!林俊贤向我走来了!!!】
但是当我们点击张卫健的按钮时,理论上,林俊贤是没有变化的,但是控制台又出现了【他来了!!!林俊贤向我走来了!!!】
说明不管谁变化,都要重新渲染一次,这显然是不符合我们要求的
所以,我们要用useMemo解决这个问题
import React,{useState , useMemo} from ‘react‘;
function Example8(){
const [junxian,setJunxian] = useState(‘林俊贤在接客状态‘);
const [dkzhang,setDkzhang] = useState(‘张卫健在接客状态‘);
return(
<div>
<button onClick={()=>{setJunxian(new Date().getTime())}}>林俊贤</button>
<button onClick={()=>{setDkzhang(new Date().getTime()+‘DK向我走来!‘)}}>张卫健</button>
<ChildComponent name={junxian}>{dkzhang}</ChildComponent>
</div>
)
}

function ChildComponent({name , children}){
function changeJunxian(){
console.log(‘他来了!!!林俊贤向我走来了!!!‘)
return name+‘,林俊贤向我走来了!‘
}
// const actionJunxian = changeJunxian(name);

//当我们调用的函数有返回值的时候,不用加{},当我们加{}的时候,就必须里面有return
const actionJunxian = useMemo(()=>changeJunxian(name),[name]);
return(
<div>
<div>{actionJunxian}</div>
<div>{children}</div>
</div>
)
}

export default Example8;

关于箭头函数里面要不要大括号{},当我们调用一个函数里面有return的时候,不加{},当我们调用的函数没有返回值,我们要用{},并且,我们要在{}里面return一个返回值:https://www.cnblogs.com/yangxuanxuan/p/11174334.html

26.6.useRef()获取dom元素和保存变量
这就是我们的example9.js
import React,{useRef,useState,useEffect} from ‘react‘;

function Example9(){
const inputEl = useRef(null);//也可以不传值
const onButtonClick = ()=>{
inputEl.current.value = textRef.current;
console.log(inputEl);
}
const [text , setText] = useState(‘dkzhang‘);
const textRef = useRef(null);
useEffect(()=>{
textRef.current = text;
console.log(textRef)
console.log(‘textRef.current:‘+textRef.current)
})
return(
<div>
<input ref={inputEl} type=‘text‘ />
<button onClick = {onButtonClick}>在input上展示文字</button>
<br />
<br />
<input value={text} onChange={(e)=>{setText(e.target.value)}} />
</div>
)
}
export default Example9;

这里要注意,我们用ref的时候,如果赋给了ref属性,那么他的结构是一个inputEl.current.value,才会出来值的
但是如果是直接赋值的话,就直接textRef.current就可以,也就是说,他只有current一个属性,没有别的
但是如果是赋值为ref属性,那么它的current除了value属性,还有别的很多属性,不能直接inputEl.current赋值

26.7.自定义hooks
自定义hooks的函数开头必须是use,这是默认的规定
import React,{useState,useEffect,useCallback} from ‘react‘;
function useWinSize(){
//初始值用一个对象
const [size , setSize] = useState({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
});

const onResize = useCallback(()=>{
setSize({
width:document.documentElement.clientWidth,
height:document.documentElement.clientHeight
})
},[])

useEffect(()=>{
window.addEventListener(‘resize‘,onResize)
return ()=>{
window.removeEventListener(‘resize‘,onResize);
}
},[])

return size;
}
//以上就是自定义了一个hooks

function Example10(){
const size = useWinSize();
return (
<div>页面大小size:{size.width}X{size.height}</div>
)
}
export default Example10;

以上是关于React - Redux Hooks的使用细节详解的主要内容,如果未能解决你的问题,请参考以下文章

使用 React-Redux Hooks 和 React-Redux Connect() 的主要区别是啥?

React Hooks 与 React-redux

如何使用 React hooks 和 Redux 从 useEffect 执行 store.unsubscribe

如何将 redux-sagas 与 react-hooks 一起使用

等待带有 React Hooks 的 Redux Action

使用 Redux 在 React Hooks 应用程序中添加 Storybook 旋钮