先附上package.json
{
"name": "transfer",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.1",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"react-intl": "3.2.0",
"prop-types": "latest",
"axios": "0.21.1"
},
"devDependencies": {
"redux-devtools-extension": "^2.13.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
1.第一个demo,使用属性props,通过父组件实现兄弟组件之间的值传递
1.1 在src下创建component目录,用来存放组件
1.2 在component目录下创建transferValue目录,存放第一个值传递demo的组件
1.3 在transferValue目录下创建子组件 ChildSearch.jsx
import React from 'react'
import PropTypes from 'prop-types'
export default class ChildSearch extends React.Component {
state = {
keyword: ''
};
static propTypes = {
setKeyword: PropTypes.func.isRequired
};
render() {
return (
<div style={{width: 200, high: 200, backgroundColor: '#eeabbc', padding: 50}}>
<h3>搜索用户组件</h3>
<input type="text" placeholder="请输入搜索关键字" name="keyword"
onChange={(e) => this.keywordInputOnChangeHandler(e)}/>
<input type="button" value="开始搜索" onClick={() => this.searchClickHandler()}/>
</div>
)
}
/**
* 输入框内容改变事件
*
* @param e 事件
*/
keywordInputOnChangeHandler = (e) => {
const inputKeyword = e.target.value;
this.setState({
keyword: inputKeyword
}, () => {
console.log("1.【搜索组件】输入框搜索内容 keyword = ", this.state.keyword)
})
};
/**
* 点击事件
*/
searchClickHandler = () => {
const {keyword} = this.state;
console.log(`2.【搜索组件】触发点击事件 keyword.trim()=${keyword.trim()}`);
if (!keyword.trim()) return;
console.log("3.【搜索组件】通过props属性方法setKeyword,调用父组件setKeyword方法");
this.props.setKeyword(keyword)
}
}
1.4 在transferValue目录下创建子组件 ChildList.jsx
import React from 'react'
import PropTypes from 'prop-types'
import Axios from 'axios'
export default class ChildList extends React.Component {
static propTypes = {
keyword: PropTypes.string.isRequired,
};
state = {
userList: [],
};
componentWillReceiveProps(newProps) {
const {keyword} = newProps;
console.log("5.【列表组件】列表组件获取到搜索组件的keyword,keyword = ", keyword);
// Axios.get(`http://localhost:3000/search?keyword=${keyword}`)
// .then(res => {
// if (res.data.code === 0) {
// const userList = res.data.data;
// console.log(`userList=${JSON.stringify(userList)}`);
// this.setState({userList})
// }
// })
const ResultUserList = [{id: keyword, name: '刘德华', age: 58}];
console.log(`6.【列表组件】从ajax返回的结果userList=${JSON.stringify(ResultUserList)}`);
this.setState({
userList: ResultUserList,
}, () => {
console.log("6.1【列表组件】修改state.userList值,state = ", this.state);
});
}
render() {
return (
<div style={{width: 200, high: 200, backgroundColor: '#aaaaff', padding: 50}}>
{
console.log("7.【列表组件】渲染页面列表")
}
<p>列表结果组件</p>
{
this.state.userList.map(user => <p key={user.id}>{user.id + "--" + user.name + "--" + user.age}</p>)
}
</div>
)
}
}
1.5 在transferValue目录下创建父组件 Root.jsx
import React from 'react';
import ChildSearch from './ChildSearch';
import ChildList from './ChildList';
export default class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
keyword: ''
};
this.setKeyword = this.setKeyword.bind(this)
}
setKeyword(inputKeyword) {
console.log(`4.【Root组件】的setKeyword被执行,修改state.keyword值, keyword=${inputKeyword}`);
this.setState({
keyword: inputKeyword
}, () => {
console.log("4.1【Root组件】中的state.keyword值被改变 state = ", this.state)
})
}
render() {
return (
<div>
<ChildSearch setKeyword={this.setKeyword}/>
<ChildList keyword={this.state.keyword}/>
</div>
)
}
}
1.6 修改index.js文件
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Root from "./component/transferValue/Root";
ReactDOM.render(
<Root/>,
document.getElementById('root')
);
2. 第二个demo,使用属性props,通过父组件实现兄弟组件之间的值传递
2.1 在component目录下创建transferValue2目录,存放第二个值传递demo的组件
2.2 在transferValue2目录下创建子组件 Son1.jsx
import React from 'react';
import PropTypes from 'prop-types'
class Son1 extends React.Component {
static propTypes = {
msg: PropTypes.func.isRequired,
};
constructor() {
super()
}
click = () => {
// 在这传给父组件一个值
console.log("1.【Son1组件】点击Son1按钮传值给父组件");
const value = '这是Son1传过来的';
this.props.msg(value)
};
render() {
return (
<React.Fragment>
{/* 写一个点击按钮 来调用上边的值*/}
<div style={{width: 200, high: 200, backgroundColor: '#eeabbc', padding: 50}}>
<p>Son1</p>
<button onClick={this.click}>Son1中的按钮</button>
</div>
</React.Fragment>
)
}
}
export default Son1;
2.3 在transferValue2目录下创建子组件 Son2.jsx
import React from 'react';
import PropTypes from 'prop-types'
class Son2 extends React.Component {
static propTypes = {
str: PropTypes.string.isRequired,
};
constructor() {
super();
}
render() {
return (
<React.Fragment>
{/* 第二个子组件通过props 这个属性来接受父组件传过来的str */}
<div style={{width: 200, high: 200, backgroundColor: '#aaaaff', padding: 50}}>
<p>Son2</p>
<p>接收Son1的值: </p>
{
console.log("3.【Son2组件】从props属性str中获取到父组件的this.state.message值, this.props.str = ", this.props.str)
}
<p>{this.props.str}</p>
</div>
</React.Fragment>
)
}
}
export default Son2;
2.4 在transferValue2目录下创建父组件 Father.jsx
import React from 'react';
import Son1 from './Son1';
import Son2 from './Son2'
class Father extends React.Component {
constructor() {
super();
// 先给message一个空值
this.state = {
message: ''
};
}
// 声明一个方法用来接收Son1传来的值
getMessage = (msg) => {
console.log("2.【父组件】调用Son1组件的msg属性方法接收Son1传来的值 msg = ", msg);
this.setState({
message: msg // 把Son1传来的值给message
}, () => {
console.log("2.1 【父组件】把Son1传来的值给state.message, state = ", this.state)
});
};
render() {
return (
<React.Fragment>
{/* 我们在这里引入子组件 拿到上边声明的方法 */}
<Son1 msg={this.getMessage}/>
{/* 这里引入第二个子组件 并声明一个属性str 把Son1传来的值传过去 */}
<Son2 str={this.state.message}/>
</React.Fragment>
)
}
}
export default Father;
2.5 修改index.js 内容
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Father from "./component/transferValue2/Father";
ReactDOM.render(
<Father/>,
document.getElementById('root')
);
3 第三个demo,使用 Redux 实现兄弟组件之间的值传递
3.1 在component目录下创建transferValue3-redux目录,存放第三个值传递demo的组件
3.2 在transferValue3-redux目录下创建子组件 AddItemChild.jsx
import React, {Component} from "react";
import {connect} from "react-redux";
import {getChangeValueAction, getSubmitValueAction} from "./store/actions";
import store from "./store/store";
class AddItemChild extends Component {
constructor() {
super();
// TODO 获取数据【Reducer(记事本)处理好的新数据】。。。 借书人 (component组件) 向图书管理员 (store) 借书
this.state = store.getState(); // store可以通过getState()方法获取图书信息(数据)
// TODO component组件(借书人) 订阅 store(图书管理员) 里面的数据
store.subscribe(() => this.changeStore()); // 如果数据改变了,就会执行传入subscribe方法的参数
}
render() {
return (
<div style={{width: 200, high: 200, backgroundColor: '#eeabbc', padding: 50}}>
<p style={{color: 'green'}}>添加数据组件</p>
<input value={this.state.inputVal} onChange={(e) => this.changeInput(e)} placeholder="to do item"/>
<button onClick={() => this.handleSubmit()}>提交</button>
</div>
);
}
changeInput(e) {
console.log("1.【添加数据组件】输入框的值改变, 输入值 = ", e.target.value);
let action = getChangeValueAction(e.target.value);
// TODO store(图书管理员) 监听 component组件(借书人) 的动作
store.dispatch(action);
}
handleSubmit() {
console.log("5.【添加数据组件】点击提交按钮提交数据, inputVal = ", this.state.inputVal);
let action = getSubmitValueAction(this.state.inputVal);
// TODO store(图书管理员) 监听 component组件(借书人) 的动作
store.dispatch(action);
}
changeStore() {
console.log("3.【添加数据组件】将从store(图书管理员)获取到的新数据,重新渲染component组件(借书人)里面的数据,state =", store.getState());
this.setState(store.getState(), () => {
console.log("3.1【添加数据组件】component组件(借书人)重新渲染后的 state = ", store.getState());
}); // TODO 将从 store(图书管理员) 获取到的【新数据】重新渲染 component组件(借书人) 里面的数据
}
}
// 使用react-redux向外暴露组件
export default connect()(AddItemChild);
3.3 在transferValue3-redux目录下创建子组件 ListItemChild.jsx
import React, {Component} from "react";
import {connect} from "react-redux";
import {getDeleteValueAction} from "./store/actions";
import store from "./store/store";
class ListItemChild extends Component {
constructor() {
super();
// TODO 获取数据【Reducer(记事本)处理好的新数据】。。。 借书人 (component组件) 向图书管理员 (store) 借书
this.state = store.getState(); // store可以通过getState()方法获取图书信息(数据)
// TODO component组件(借书人) 订阅 store(图书管理员) 里面的数据
store.subscribe(() => this.changeStore()); // 如果数据改变了,就会执行传入subscribe方法的参数
}
render() {
return (
<div style={{width: 200, high: 200, backgroundColor: '#aaaaff', padding: 50}}>
<p style={{color: 'green'}}>显示列表组件</p>
{
// TODO 渲染到页面(借书人component组件)
this.state.list.map((item, index) => {
return <div key={index} onClick={this.delItem.bind(this, index)}>{item}</div>
})
}
</div>
);
}
delItem(index) {
let action = getDeleteValueAction(index);
// TODO store(图书管理员) 监听 component组件(借书人) 的动作
store.dispatch(action);
}
changeStore() {
console.log("4.【列表组件】将从 store(图书管理员) 获取到的【新数据】重新渲染 component组件(借书人) 里面的数据 state = ", store.getState());
this.setState(store.getState(), () => {
console.log("4.1【列表组件】component组件(借书人)重新渲染后的 state = ", store.getState());
}); // TODO 将从 store(图书管理员) 获取到的【新数据】重新渲染 component组件(借书人) 里面的数据
}
}
// 使用react-redux向外暴露组件
export default connect()(ListItemChild);
3.4 在transferValue3-redux目录下创建父组件 TodoRoot.jsx
import React, {Component} from "react";
import {connect} from "react-redux";
import AddItemChild from "./AddItemChild";
import ListItemChild from "./ListItemChild";
class TodoRoot extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div style={{position: 'absolute', top: '30%', left: '30%'}}>
<AddItemChild/>
<ListItemChild/>
</div>
)
}
}
// export default TodoList;
// 使用react-redux向外暴露组件
export default connect()(TodoRoot);
3.5 在transferValue3-redux目录下创建store目录,在store目录下分别创建4个js文件:action.js , actionType.js , reducer.js , store.js
3.6 action.js 文件:
import {INPUT_CHANGE_VALUE, INPUT_SUBMIT_VALUE, INPUT_DELETE_VALUE} from "./actionTypes.js"
/**
* 统一创建action
*
* 之前代码我们把action的创建都放在了组件中,写一些简单的项目确实可以这样做,但是如果是大型项目,
* 就需要将创建action这个动作放入到一个文件进行统一的管理,这样代码的可维护性就比较高了
*/
export let getChangeValueAction = (value) => {
return {
type: INPUT_CHANGE_VALUE,
value,
}
};
export let getSubmitValueAction = (value) => {
return {
type: INPUT_SUBMIT_VALUE,
value,
}
};
export let getDeleteValueAction = (value) => {
return {
type: INPUT_DELETE_VALUE,
value,
}
};
3.7 actionType.js 文件:
export const INPUT_CHANGE_VALUE="input_change_value";
export const INPUT_SUBMIT_VALUE="input_submit_value";
export const INPUT_DELETE_VALUE="input_delete_value";
3.8 reducer.js 文件:
import {combineReducers} from "redux";
import {INPUT_CHANGE_VALUE, INPUT_SUBMIT_VALUE, INPUT_DELETE_VALUE} from "./actionTypes.js"
let defaultState = {
inputVal: "",
list: ["《三国演义》", "《水浒传》", "《西游记》", "《红楼梦》"],
};
/**
* 记事本 (reducer)
*/
// TODO defaultState 是这个图书馆所有书籍的存放的原始信息,
// TODO action 动作(借书人说我要借书)
export default (state = defaultState, action) => {
console.log(state, action);
/*
Reducer(记事本) 会得到原始store(图书管理员)里面的数据,并得知有哪个数据要改变,还有改变后数据的值,
知道这些后,Reducer(记事本)会将处理好的新数据返回给store(图书管理员)
注意:
reducer(记事本)虽然能获取到store(图书管理员)里面的数据,但是它不能直接将数据进行修改,只能对拷贝后的数据修改,最后返回给store(图书管理员)
*/
if (action.type === INPUT_CHANGE_VALUE) {
let newState = JSON.parse(JSON.stringify(state));
newState.inputVal = action.value;
console.log("2.【reducer】记事本将处理好的新数据返回给store(图书管理员), newState = ", newState);
return newState; // TODO Reducer(记事本)会将处理好的 【新数据】 返回给store(图书管理员)
} else if (action.type === INPUT_SUBMIT_VALUE) {
let newState = JSON.parse(JSON.stringify(state));
newState.inputVal = "";
newState.list.push(action.value);
console.log("6.【reducer】记事本将处理好的新数据返回给store(图书管理员), newState = ", newState);
return newState;
} else if (action.type === INPUT_DELETE_VALUE) {
let newState = JSON.parse(JSON.stringify(state));
newState.list.splice(action.value, 1);
return newState;
}
console.log("臥槽");
return state; // TODO state 是记录本 (reducer) 返回给图书管理员(store) 的图书信息
}
// TODO 这个方式暴露有问题
// export default combineReducers({
// fun
// })
3.9 store.js 文件:
import {createStore, applyMiddleware} from 'redux'; // 引入createStore来创建store
import thunk from 'redux-thunk' // 应用上异步中间件
import {composeWithDevTools} from 'redux-devtools-extension'
import reducer from './reducer'; // 记事本
/**
* 图书管理员 (store)
*/
// TODO 图书管理员(Store)并不知道图书馆里都放了哪些书还有书的位置, 他要借助记录本(reducer)来查看图书的信息
const store = createStore(
reducer,
composeWithDevTools(applyMiddleware(thunk)) // 应用上异步中间件
);
export default store;
// 参数:window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
// 用来方便调试,首先应该安装react-devtools调试工具,方便以后数据的查看,下载好了直接拖拽到谷歌浏览器的扩展窗口即可使用
3.9 修改index.js内容
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import './index.css';
import store from './component/transferValue3-redux/store/store';
import Root from "./component/transferValue/Root";
import Father from "./component/transferValue2/Father";
import TodoRoot from "./component/transferValue3-redux/TodoRoot";
// ReactDOM.render(
// <Root/>,
// document.getElementById('root')
// );
// ReactDOM.render(
// <Father/>,
// document.getElementById('root')
// );
ReactDOM.render(
(
<Provider store={store}>
<TodoRoot/>
</Provider>
),
document.getElementById('root')
);
最后附上项目目录结构: