我正在尝试将d3与react结合使用以生成图以最终实现排序算法可视化工具。我在render函数中有代码return <svg ref={node=>this.node=node}/>
,并且在名为renderSvg()
的函数中调用了节点,该函数在componentDidUpdate
中进行了调用。看来道具没有更新。
我已经进行了一些研究,并且了解最常见的原因是状态更新时的状态。但是,我已经检查了我的代码,无论如何我似乎都没有修改原始状态。在任何状态部分中都没有任何对象,但是在有数组的地方,我使用了散布运算符来确保不修改原始对象。我想知道是否与我正在使用Redux.combineReducers
有关。我不是要测试实际的排序算法,是的,我仍在尝试获取随机生成的数组以在svg中可视化呈现。请注意,如果有任何区别,我也会使用reduxThunk
。
有人可以帮忙吗?如果它是combineReducers
函数,是否可以解决? Here is a link to my project,下面是我的代码:
$(function(){
//REDUC
//array action types
const GENERATE_ARRAY = 'GENERATE_ARRAY';
const SORT_ARRAY = 'SORT_ARRAY';
//accessCount action types
const INCREMENT_ACCESS_COUNTER = 'INCREMENT_ACCESS_COUNTER';
const RESET_ACCESS_COUNTER = 'RESET_ACCESS_COUNTER';
//indexing action types
const ACCESSING_INDICES = 'ACCESSING_INDICES'
const MIN_INDEX = 'MIN_INDEX';
const MAX_INDEX = 'MAX_INDEX';
const SORTED_INDICES = 'SORTED_INDICES';
//array action generators
function generateArray(value){
return {type: GENERATE_ARRAY, value};
}
function sortArray(algorithm){
return {type: SORT_ARRAY, algorithm};
}
//accessCounter action generators
function incrementAccessCounter(){
return {type: INCREMENT_ACCESS_COUNTER};
}
function resetAccessCounter(){
return {type: RESET_ACCESS_COUNTER};
}
//sortedIndices and accessingIndices action generators
function updateSortedIndices(value){
return {type: SORTED_INDICES, value};
}
function updateAccessingIndices(value){
return {type: ACCESSING_INDICES, value};
}
//max and min index action generators
function setMinIndex(index){
return {type: MIN_INDEX, index};
}
function setMaxIndex(index){
return {type: MAX_INDEX, index};
}
//initial values
const INITIAL_ARRAY = [];
const INITIAL_INDEX = -1;
const INITIAL_ACCESS_COUNTER = 0;
const INITIAL_STATE = {
array: [],
accessCounter: INITIAL_ACCESS_COUNTER,
sortedIndices: [],
accessingIndices: [],
minIndex: INITIAL_INDEX,
maxIndex: INITIAL_INDEX
}
//state reducers
function arrayReducer(state = [], action){
switch(action.type){
case GENERATE_ARRAY:
if (Array.isArray(action.value))
return action.value;
let result = [];
for (let i = 0; i < action.value; i++)
result.push(Math.random());
return result;
default: return [...state];
}
}
function sortedIndicesReducer(state = [], action){
switch(action.type){
case SORTED_INDICES:
if (Array.isArray(action.value))
return [...action.value];
return [...state, action.value];
default: return [...state];
}
}
function accessingIndicesReducer(state = [], action){
switch(action.type){
case ACCESSING_INDICES:
if (Array.isArray(action.value))
return [...action.value];
return [...state, action.value];
default: return [...state];
}
}
function accessCounterReducer(state = INITIAL_ACCESS_COUNTER, action){
switch(action.type){
case INCREMENT_ACCESS_COUNTER: return state + 1;
case RESET_ACCESS_COUNTER: return 0;
default: return state;
}
}
function minIndexReducer(state = INITIAL_INDEX, action){
switch(action.type){
case MIN_INDEX: return action.index;
default: return state;
}
}
function maxIndexReducer(state = INITIAL_INDEX, action){
switch(action.type){
case MAX_INDEX: return action.index;
default: return state;
}
}
const rootReducer = Redux.combineReducers({
array: arrayReducer,
accessCounter: accessCounterReducer,
sortedIndices: sortedIndicesReducer,
accessingIndices: accessingIndicesReducer,
minIndex: minIndexReducer,
maxIndex: maxIndexReducer
});
const store = Redux.createStore(rootReducer, Object.assign({},INITIAL_STATE), Redux.applyMiddleware(ReduxThunk.default));
//sorting algorithms
const sortingAlgorithms = [
{
name: 'Selection Sort',
algorithm: function(){
return function(dispatch, getState){
dispatch(resetAccessCounter());
dispatch(updateSortedIndices([]));
let array = [...getState().array];
let sorted = [...getState().sortedIndices];
for (let i = 0; i < array.length; i++){
let minIndex = i;
for (let j = 0; j < array.length; j++){
let accessing = j;
minIndex = array[j] < array[minIndex]
? j : minIndex;
dispatch(incrementAccessCounter());
dispatch(updateAccessingIndices([accessing]));
if (minIndex != j)
dispatch(setMinIndex(minIndex));
}
dispatch(updateAccessingIndices([]));
let tmp = array[i];
array.splice(i, 1, array[minIndex]);
array.splice(minIndex, 1, tmp);
dispatch(setMinIndex(-1));
dispatch(generateArray(array));
dispatch(updateSortedIndices(i));
}
}
}
},
{
name: 'Bubble Sort',
algorithm: function(){
return function(dispatch, getState){
};
}
}
];
//React
class Form extends React.Component{
constructor(props){
super(props);
this.state = {
length: '1000',
algorithm: sortingAlgorithms[0].algorithm
}
this.algorithmHandler = this.algorithmHandler.bind(this);
this.lengthHandler = this.lengthHandler.bind(this);
this.generateHandler = this.generateHandler.bind(this);
this.sortHandler = this.sortHandler.bind(this);
}
algorithmHandler(event){
this.setState({algorithm: event.target.value});
}
lengthHandler(event){
this.setState({length: event.targer.value});
}
generateHandler(event){
event.preventDefault();
console.log('before:', this.props, store.getState());
this.props.generateArray(parseInt(this.state.length));
console.log('after:', this.props, store.getState());
}
sortHandler(event){
event.preventDefault();
this.props.sort(this.state.algorithm);
}
render(){
return(
<div>
<h1 class='text-center'>SortingAlgorithms</h1>
<form>
<label for='algorithm'>
Algorithm:
<select
id='algorithm'
class='form-control'
value={this.state.algorithm}
onChange={this.algorithmHander}>
{sortingAlgorithms.map(function(e){
return (
<option value={e.algorithm}>
{e.name}
</option>
);
})}
</select>
</label>
<label for='array-length'>
<input
id='array-length'
class='form-control'
type='number'
min='10'
max='10000'
value={this.state.length}
onChange={this.lenghtHandler}/>
</label>
<button
id='generate'
class='btn btn-primary'
onClick={this.generateHandler}>
Generate Random Array
</button>
<button
id='sort'
class='btn btn-primary'
onClick={this.sortHandler}>
Sort Array
</button>
</form>
</div>
)
}
}
class Svg extends React.Component{
constructor(props){
super(props);
this.renderSvg = this.renderSvg.bind(this);
}
componenDidUpdate(){
this.renderSvg();
}
componentDidMount(){
this.renderSvg();
}
renderSvg(){
const svgPadding = 50;
const svgWidth = 900;
const svgHeight = 540;
var svg = d3.select(this.node)
.attr('width', svgWidth)
.attr('height', svgHeight);
//scales
const xScale = d3.scaleBand()
.domain([0, this.props.data.length])
.range([svgPadding, svgWidth-svgPadding]);
const yScale = d3.scaleLinear()
.domain(d3.extent(this.props.data))
.range([svg-svgHeight, svgPadding]);
svg.selectAll('.bar')
.data(this.props.data)
.enter().append('rect')
.attr('class', 'bar')
.attr('x', (d, i)=>xScale(i))
.attr('y', d=>yScale(d))
.attr('fill', 'black');
}
render(){
return <svg ref={node=>this.node=node}/>
}
}
//ReactRedux
const Provider = ReactRedux.Provider;
const connect = ReactRedux.connect;
function mapStateToProps(state){
return {
data: state.array,
accessCounter: state.accessCounter,
sortedIndices: state.sortedIndices,
accessingIndices: state.accessingIndices,
minIndex: state.minIndex,
maxIndex: state.maxIndex
};
}
function mapDispatchToProps(dispatch){
return {
generateArray: length => dispatch(generateArray(length)),
sort: algorithm => dispatch(algorithm())
};
}
const FormConnection = connect(mapStateToProps, mapDispatchToProps)(Form);
const SvgConnection = connect(mapStateToProps, null)(Svg);
class Wrapper extends React.Component{
render(){
return(
<Provider store={store}>
<FormConnection/>
<SvgConnection/>
</Provider>
);
}
}
ReactDOM.render(<Wrapper/>, $('body')[0]);
})